def make_clamper(datapos, mempos, typ, is_init=False): if not is_init: data_decl = ['calldataload', ['add', 4, datapos]] copier = lambda pos, sz: ['calldatacopy', mempos, ['add', 4, pos], sz] else: data_decl = ['codeload', ['add', '~codelen', datapos]] copier = lambda pos, sz: [ 'codecopy', mempos, ['add', '~codelen', pos], sz ] # Numbers: make sure they're in range if is_base_type(typ, 'int128'): return LLLnode.from_list([ 'clamp', ['mload', MemoryPositions.MINNUM], data_decl, ['mload', MemoryPositions.MAXNUM] ], typ=typ, annotation='checking int128 input') # Booleans: make sure they're zero or one elif is_base_type(typ, 'bool'): return LLLnode.from_list(['uclamplt', data_decl, 2], typ=typ, annotation='checking bool input') # Addresses: make sure they're in range elif is_base_type(typ, 'address'): return LLLnode.from_list( ['uclamplt', data_decl, ['mload', MemoryPositions.ADDRSIZE]], typ=typ, annotation='checking address input') # Bytes: make sure they have the right size elif isinstance(typ, ByteArrayType): return LLLnode.from_list([ 'seq', copier(data_decl, 32 + typ.maxlen), [ 'assert', ['le', ['calldataload', ['add', 4, data_decl]], typ.maxlen] ] ], typ=None, annotation='checking bytearray input') # Lists: recurse elif isinstance(typ, ListType): o = [] for i in range(typ.count): offset = get_size_of_type(typ.subtype) * 32 * i o.append( make_clamper(datapos + offset, mempos + offset, typ.subtype, is_init)) return LLLnode.from_list(['seq'] + o, typ=None, annotation='checking list input') # Otherwise don't make any checks else: return LLLnode.from_list('pass')
def call_make_placeholder(stmt_expr, context, sig): if sig.output_type is None: return 0, 0, 0 output_placeholder = context.new_placeholder(typ=sig.output_type) out_size = get_size_of_type(sig.output_type) * 32 returner = output_placeholder if not sig.private and isinstance(sig.output_type, ByteArrayType): returner = output_placeholder + 32 return output_placeholder, returner, out_size
def new_variable(self, name, typ): if not is_varname_valid(name, custom_units=self.custom_units): raise VariableDeclarationException( "Variable name invalid or reserved: " + name) if any((name in self.vars, name in self.globals, name in self.constants)): raise VariableDeclarationException("Duplicate variable name: %s" % name) self.vars[name] = VariableRecord(name, self.next_mem, typ, True, self.blockscopes.copy()) pos = self.next_mem self.next_mem += 32 * get_size_of_type(typ) return pos
def get_external_contract_call_output(sig, context): if not sig.output_type: return 0, 0, [] output_placeholder = context.new_placeholder(typ=sig.output_type) output_size = get_size_of_type(sig.output_type) * 32 if isinstance(sig.output_type, BaseType): returner = [0, output_placeholder] elif isinstance(sig.output_type, ByteArrayType): returner = [0, output_placeholder + 32] else: raise TypeMismatchException("Invalid output type: %s" % sig.output_type) return output_placeholder, output_size, returner
def from_declaration(cls, code, custom_units=None): name = code.target.id pos = 0 if not is_varname_valid(name, custom_units=custom_units): raise EventDeclarationException("Event name invalid: " + name) # Determine the arguments, expects something of the form def foo(arg1: num, arg2: num ... args = [] indexed_list = [] topics_count = 1 if code.annotation.args: keys = code.annotation.args[0].keys values = code.annotation.args[0].values for i in range(len(keys)): typ = values[i] arg = keys[i].id is_indexed = False # Check to see if argument is a topic if isinstance(typ, ast.Call) and typ.func.id == 'indexed': typ = values[i].args[0] indexed_list.append(True) topics_count += 1 is_indexed = True else: indexed_list.append(False) if isinstance(typ, ast.Subscript) and getattr(typ.value, 'id', None) == 'bytes' and typ.slice.value.n > 32 and is_indexed: raise EventDeclarationException("Indexed arguments are limited to 32 bytes") if topics_count > 4: raise EventDeclarationException("Maximum of 3 topics {} given".format(topics_count - 1), arg) if not isinstance(arg, str): raise VariableDeclarationException("Argument name invalid", arg) if not typ: raise InvalidTypeException("Argument must have type", arg) if not is_varname_valid(arg, custom_units): raise VariableDeclarationException("Argument name invalid or reserved: " + arg, arg) if arg in (x.name for x in args): raise VariableDeclarationException("Duplicate function argument name: " + arg, arg) parsed_type = parse_type(typ, None, custom_units=custom_units) args.append(VariableRecord(arg, pos, parsed_type, False)) if isinstance(parsed_type, ByteArrayType): pos += ceil32(typ.slice.value.n) else: pos += get_size_of_type(parsed_type) * 32 sig = name + '(' + ','.join([canonicalize_type(arg.typ, indexed_list[pos]) for pos, arg in enumerate(args)]) + ')' # noqa F812 event_id = bytes_to_int(sha3(bytes(sig, 'utf-8'))) return cls(name, args, indexed_list, event_id, sig)
def _RLPlist(expr, args, kwargs, context): # Second argument must be a list of types if not isinstance(args[1], ast.List): raise TypeMismatchException( "Expecting list of types for second argument", args[1]) if len(args[1].elts) == 0: raise TypeMismatchException("RLP list must have at least one item", expr) if len(args[1].elts) > 32: raise TypeMismatchException("RLP list must have at most 32 items", expr) # Get the output format _format = [] for arg in args[1].elts: if isinstance(arg, ast.Name) and arg.id == "bytes": subtyp = ByteArrayType(args[0].typ.maxlen) else: subtyp = parse_type(arg, 'memory') if not isinstance(subtyp, BaseType): raise TypeMismatchException( "RLP lists only accept BaseTypes and byte arrays", arg) if not is_base_type( subtyp, ('int128', 'uint256', 'bytes32', 'address', 'bool')): raise TypeMismatchException( "Unsupported base type: %s" % subtyp.typ, arg) _format.append(subtyp) output_type = TupleType(_format) output_placeholder_type = ByteArrayType( (2 * len(_format) + 1 + get_size_of_type(output_type)) * 32) output_placeholder = context.new_placeholder(output_placeholder_type) output_node = LLLnode.from_list(output_placeholder, typ=output_placeholder_type, location='memory') # Create a decoder for each element in the tuple decoder = [] for i, typ in enumerate(_format): # Decoder for bytes32 if is_base_type(typ, 'bytes32'): decoder.append( LLLnode.from_list( [ 'seq', [ 'assert', [ 'eq', [ 'mload', [ 'add', output_node, [ 'mload', ['add', output_node, 32 * i] ] ] ], 32 ] ], [ 'mload', [ 'add', 32, [ 'add', output_node, ['mload', ['add', output_node, 32 * i]] ] ] ] ], typ, annotation='getting and checking bytes32 item')) # Decoder for address elif is_base_type(typ, 'address'): decoder.append( LLLnode.from_list( [ 'seq', [ 'assert', [ 'eq', [ 'mload', [ 'add', output_node, [ 'mload', ['add', output_node, 32 * i] ] ] ], 20 ] ], [ 'mod', [ 'mload', [ 'add', 20, [ 'add', output_node, [ 'mload', ['add', output_node, 32 * i] ] ] ] ], ['mload', MemoryPositions.ADDRSIZE] ] ], typ, annotation='getting and checking address item')) # Decoder for bytes elif isinstance(typ, ByteArrayType): decoder.append( LLLnode.from_list([ 'add', output_node, ['mload', ['add', output_node, 32 * i]] ], typ, location='memory', annotation='getting byte array')) # Decoder for num and uint256 elif is_base_type(typ, ('int128', 'uint256')): bytez = LLLnode.from_list( ['add', output_node, ['mload', ['add', output_node, 32 * i]]], typ, location='memory', annotation='getting and checking %s' % typ.typ) decoder.append(byte_array_to_num(bytez, expr, typ.typ)) # Decoder for bools elif is_base_type(typ, ('bool')): # This is basically a really clever way to test for a length-prefixed one or zero. We take the 32 bytes # starting one byte *after* the start of the length declaration; this includes the last 31 bytes of the # length and the first byte of the value. 0 corresponds to length 0, first byte 0, and 257 corresponds # to length 1, first byte \x01 decoder.append( LLLnode.from_list([ 'with', '_ans', [ 'mload', [ 'add', 1, [ 'add', output_node, ['mload', ['add', output_node, 32 * i]] ] ] ], [ 'seq', [ 'assert', ['or', ['eq', '_ans', 0], ['eq', '_ans', 257]] ], ['div', '_ans', 257] ] ], typ, annotation='getting and checking bool')) else: # Should never reach because of top level base level check. raise Exception("Type not yet supported") # pragma: no cover # Copy the input data to memory if args[0].location == "memory": variable_pointer = args[0] elif args[0].location == "storage": placeholder = context.new_placeholder(args[0].typ) placeholder_node = LLLnode.from_list(placeholder, typ=args[0].typ, location='memory') copier = make_byte_array_copier( placeholder_node, LLLnode.from_list('_ptr', typ=args[0].typ, location=args[0].location)) variable_pointer = [ 'with', '_ptr', args[0], ['seq', copier, placeholder_node] ] else: # Should never reach because of top level base level check. raise Exception("Location not yet supported") # pragma: no cover # Decode the input data initial_setter = LLLnode.from_list([ 'seq', [ 'with', '_sub', variable_pointer, [ 'pop', [ 'call', 1500 + 400 * len(_format) + 10 * len(args), LLLnode.from_list(RLP_DECODER_ADDRESS, annotation='RLP decoder'), 0, ['add', '_sub', 32], ['mload', '_sub'], output_node, 64 * len(_format) + 32 + 32 * get_size_of_type(output_type) ] ] ], ['assert', ['eq', ['mload', output_node], 32 * len(_format) + 32]] ], typ=None) # Shove the input data decoder in front of the first variable decoder decoder[0] = LLLnode.from_list(['seq', initial_setter, decoder[0]], typ=decoder[0].typ, location=decoder[0].location) return LLLnode.from_list(["multi"] + decoder, typ=output_type, location='memory', pos=getpos(expr))
def make_setter(left, right, location, pos): # Basic types if isinstance(left.typ, BaseType): right = base_type_conversion(right, right.typ, left.typ, pos) if location == 'storage': return LLLnode.from_list(['sstore', left, right], typ=None) elif location == 'memory': return LLLnode.from_list(['mstore', left, right], typ=None) # Byte arrays elif isinstance(left.typ, ByteArrayType): return make_byte_array_copier(left, right, pos) # Can't copy mappings elif isinstance(left.typ, MappingType): raise TypeMismatchException( "Cannot copy mappings; can only copy individual elements", pos) # Arrays elif isinstance(left.typ, ListType): # Cannot do something like [a, b, c] = [1, 2, 3] if left.value == "multi": raise Exception("Target of set statement must be a single item") if not isinstance(right.typ, (ListType, NullType)): raise TypeMismatchException( "Setter type mismatch: left side is array, right side is %r" % right.typ, pos) left_token = LLLnode.from_list('_L', typ=left.typ, location=left.location) if left.location == "storage": left = LLLnode.from_list(['sha3_32', left], typ=left.typ, location="storage_prehashed") left_token.location = "storage_prehashed" # Type checks if not isinstance(right.typ, NullType): if not isinstance(right.typ, ListType): raise TypeMismatchException( "Left side is array, right side is not", pos) if left.typ.count != right.typ.count: raise TypeMismatchException("Mismatched number of elements", pos) # If the right side is a literal if right.value == "multi": if len(right.args) != left.typ.count: raise TypeMismatchException("Mismatched number of elements", pos) subs = [] for i in range(left.typ.count): subs.append( make_setter(add_variable_offset(left_token, LLLnode.from_list( i, typ='int128'), pos=pos), right.args[i], location, pos=pos)) return LLLnode.from_list(['with', '_L', left, ['seq'] + subs], typ=None) # If the right side is a null elif isinstance(right.typ, NullType): subs = [] for i in range(left.typ.count): subs.append( make_setter(add_variable_offset(left_token, LLLnode.from_list( i, typ='int128'), pos=pos), LLLnode.from_list(None, typ=NullType()), location, pos=pos)) return LLLnode.from_list(['with', '_L', left, ['seq'] + subs], typ=None) # If the right side is a variable else: right_token = LLLnode.from_list('_R', typ=right.typ, location=right.location) subs = [] for i in range(left.typ.count): subs.append( make_setter( add_variable_offset(left_token, LLLnode.from_list(i, typ='int128'), pos=pos), add_variable_offset(right_token, LLLnode.from_list(i, typ='int128'), pos=pos), location, pos=pos)) return LLLnode.from_list( ['with', '_L', left, ['with', '_R', right, ['seq'] + subs]], typ=None) # Structs elif isinstance(left.typ, (StructType, TupleType)): if left.value == "multi" and isinstance(left.typ, StructType): raise Exception("Target of set statement must be a single item") if not isinstance(right.typ, NullType): if not isinstance(right.typ, left.typ.__class__): raise TypeMismatchException( "Setter type mismatch: left side is %r, right side is %r" % (left.typ, right.typ), pos) if isinstance(left.typ, StructType): for k in left.typ.members: if k not in right.typ.members: raise TypeMismatchException( "Keys don't match for structs, missing %s" % k, pos) for k in right.typ.members: if k not in left.typ.members: raise TypeMismatchException( "Keys don't match for structs, extra %s" % k, pos) else: if len(left.typ.members) != len(right.typ.members): raise TypeMismatchException( "Tuple lengths don't match, %d vs %d" % (len(left.typ.members), len(right.typ.members)), pos) left_token = LLLnode.from_list('_L', typ=left.typ, location=left.location) if left.location == "storage": left = LLLnode.from_list(['sha3_32', left], typ=left.typ, location="storage_prehashed") left_token.location = "storage_prehashed" if isinstance(left.typ, StructType): keyz = sorted(list(left.typ.members.keys())) else: keyz = list(range(len(left.typ.members))) # If the right side is a literal if right.value == "multi": if len(right.args) != len(keyz): raise TypeMismatchException("Mismatched number of elements", pos) subs = [] for i, typ in enumerate(keyz): subs.append( make_setter(add_variable_offset(left_token, typ, pos=pos), right.args[i], location, pos=pos)) return LLLnode.from_list(['with', '_L', left, ['seq'] + subs], typ=None) # If the right side is a null elif isinstance(right.typ, NullType): subs = [] for typ in keyz: subs.append( make_setter(add_variable_offset(left_token, typ, pos=pos), LLLnode.from_list(None, typ=NullType()), location, pos=pos)) return LLLnode.from_list(['with', '_L', left, ['seq'] + subs], typ=None) # If tuple assign. elif isinstance(left.typ, TupleType) and isinstance( right.typ, TupleType): right_token = LLLnode.from_list('_R', typ=right.typ, location="memory") subs = [] static_offset_counter = 0 for idx, (left_arg, right_arg) in enumerate(zip(left.args, right.typ.members)): # if left_arg.typ.typ != right_arg.typ: # raise TypeMismatchException("Tuple assignment mismatch position %d, expected '%s'" % (idx, right.typ), pos) if isinstance(right_arg, ByteArrayType): offset = LLLnode.from_list([ 'add', '_R', ['mload', ['add', '_R', static_offset_counter]] ], typ=ByteArrayType( right_arg.maxlen), location='memory', pos=pos) static_offset_counter += 32 else: offset = LLLnode.from_list( ['mload', ['add', '_R', static_offset_counter]], typ=right_arg.typ, pos=pos) static_offset_counter += get_size_of_type(right_arg) * 32 subs.append( make_setter(left_arg, offset, location="memory", pos=pos)) return LLLnode.from_list(['with', '_R', right, ['seq'] + subs], typ=None, annotation='Tuple assignment') # If the right side is a variable else: subs = [] right_token = LLLnode.from_list('_R', typ=right.typ, location=right.location) for typ in keyz: subs.append( make_setter(add_variable_offset(left_token, typ, pos=pos), add_variable_offset(right_token, typ, pos=pos), location, pos=pos)) return LLLnode.from_list( ['with', '_L', left, ['with', '_R', right, ['seq'] + subs]], typ=None) else: raise Exception("Invalid type for setters")
def pack_arguments(signature, args, context, pos, return_placeholder=True): placeholder_typ = ByteArrayType( maxlen=sum([get_size_of_type(arg.typ) for arg in signature.args]) * 32 + 32) placeholder = context.new_placeholder(placeholder_typ) setters = [['mstore', placeholder, signature.method_id]] needpos = False staticarray_offset = 0 expected_arg_count = len(signature.args) actual_arg_count = len(args) if actual_arg_count != expected_arg_count: raise StructureException( "Wrong number of args for: %s (%s args, expected %s)" % (signature.name, actual_arg_count, expected_arg_count)) for i, (arg, typ) in enumerate(zip(args, [arg.typ for arg in signature.args])): if isinstance(typ, BaseType): setters.append( make_setter(LLLnode.from_list(placeholder + staticarray_offset + 32 + i * 32, typ=typ), arg, 'memory', pos=pos)) elif isinstance(typ, ByteArrayType): setters.append([ 'mstore', placeholder + staticarray_offset + 32 + i * 32, '_poz' ]) arg_copy = LLLnode.from_list('_s', typ=arg.typ, location=arg.location) target = LLLnode.from_list(['add', placeholder + 32, '_poz'], typ=typ, location='memory') setters.append([ 'with', '_s', arg, [ 'seq', make_byte_array_copier(target, arg_copy, pos), [ 'set', '_poz', [ 'add', 32, ['ceil32', ['add', '_poz', get_length(arg_copy)]] ] ] ] ]) needpos = True elif isinstance(typ, ListType): target = LLLnode.from_list( [placeholder + 32 + staticarray_offset + i * 32], typ=typ, location='memory') setters.append(make_setter(target, arg, 'memory', pos=pos)) staticarray_offset += 32 * (typ.count - 1) else: raise TypeMismatchException("Cannot pack argument of type %r" % typ) # For private call usage, doesn't use a returner. returner = [[placeholder + 28]] if return_placeholder else [] if needpos: return (LLLnode.from_list([ 'with', '_poz', len(args) * 32 + staticarray_offset, ['seq'] + setters + returner ], typ=placeholder_typ, location='memory'), placeholder_typ.maxlen - 28, placeholder + 32) else: return (LLLnode.from_list(['seq'] + setters + returner, typ=placeholder_typ, location='memory'), placeholder_typ.maxlen - 28, placeholder + 32)
def add_variable_offset(parent, key, pos): 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, pos) if key not in typ.members: raise TypeMismatchException( "Object does not have member variable %s" % key, pos) 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)), pos) index = attrs.index(key) annotation = key else: if not isinstance(key, int): raise TypeMismatchException( "Expecting a static index; cannot access element %r" % key, pos) 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, MappingType): if 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), pos) 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], ['mload', key.args[0].args[-1]] ] ]) else: sub = LLLnode.from_list([ 'sha3', ['add', key.args[0].value, 32], ['mload', key.args[0].value] ]) else: subtype = typ.valuetype sub = base_type_conversion(key, key.typ, typ.keytype, pos=pos) if location == 'storage': return LLLnode.from_list(['sha3_64', parent, sub], typ=subtype, location='storage') elif location == 'memory': raise TypeMismatchException( "Can only have fixed-side arrays in memory, not mappings", pos) elif isinstance(typ, ListType): subtype = typ.subtype sub = [ 'uclamplt', base_type_conversion(key, key.typ, BaseType('int128'), pos=pos), typ.count ] 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': 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 ", pos) else: raise TypeMismatchException( "Cannot access the child of a constant variable! %r" % typ, pos)
def parse_func(code, sigs, origcode, global_ctx, _vars=None): if _vars is None: _vars = {} sig = FunctionSignature.from_definition( code, sigs=sigs, custom_units=global_ctx._custom_units) # Get base args for function. total_default_args = len(code.args.defaults) base_args = sig.args[: -total_default_args] if total_default_args > 0 else sig.args default_args = code.args.args[-total_default_args:] default_values = dict( zip([arg.arg for arg in default_args], code.args.defaults)) # __init__ function may not have defaults. if sig.name == '__init__' and total_default_args > 0: raise FunctionDeclarationException( "__init__ function may not have default parameters.") # Check for duplicate variables with globals for arg in sig.args: if arg.name in global_ctx._globals: raise FunctionDeclarationException( "Variable name duplicated between function arguments and globals: " + arg.name) # Create a local (per function) context. context = Context(vars=_vars, global_ctx=global_ctx, sigs=sigs, return_type=sig.output_type, is_constant=sig.const, 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, ByteArrayType) else get_size_of_type(arg.typ) * 32 for arg in sig.args ]) base_copy_size = sum([ 32 if isinstance(arg.typ, ByteArrayType) else get_size_of_type(arg.typ) * 32 for arg in base_args ]) context.next_mem += max_copy_size 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, ByteArrayType): context.vars[arg.name] = VariableRecord(arg.name, context.next_mem, arg.typ, False) context.next_mem += 32 * get_size_of_type(arg.typ) else: context.vars[arg.name] = VariableRecord( arg.name, MemoryPositions.RESERVED_MEMORY + arg.pos, arg.typ, False) # Private function copiers. No clamping for private functions. dyn_variable_names = [ a.name for a in base_args if isinstance(a.typ, ByteArrayType) ] 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( ['seq_unchecked'] + unpackers + [ 0 ], # [0] to complete full overarching 'seq' statement, see private_label. 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._custom_units) 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, ByteArrayType) 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, ByteArrayType): _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, ByteArrayType): _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, LLLnode.from_list( ['mstore', context.callback_ptr, 'pass'], annotation='pop callback pointer', pos=getpos(code)) if sig.private else ['pass'], ['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'] + _clampers + [parse_body(c, context) for c in code.body] + 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] + clampers + [parse_body(c, context) for c in code.body] + 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 from_definition(cls, code, sigs=None, custom_units=None, contract_def=False, constant=False): name = code.name pos = 0 if not is_varname_valid(name, custom_units=custom_units): raise FunctionDeclarationException("Function name invalid: " + name) # Determine the arguments, expects something of the form def foo(arg1: int128, arg2: int128 ... args = [] for arg in code.args.args: typ = arg.annotation if not typ: raise InvalidTypeException("Argument must have type", arg) if not is_varname_valid(arg.arg, custom_units=custom_units): raise FunctionDeclarationException( "Argument name invalid or reserved: " + arg.arg, arg) if arg.arg in (x.name for x in args): raise FunctionDeclarationException( "Duplicate function argument name: " + arg.arg, arg) parsed_type = parse_type(typ, None, sigs, custom_units=custom_units) args.append(VariableRecord(arg.arg, pos, parsed_type, False)) if isinstance(parsed_type, ByteArrayType): pos += 32 else: pos += get_size_of_type(parsed_type) * 32 # Apply decorators const, payable, private, public = False, False, False, False for dec in code.decorator_list: if isinstance(dec, ast.Name) and dec.id == "constant": const = True # TODO: # elif isinstance(dec, ast.Name) and dec.id == "payable": # payable = True elif isinstance(dec, ast.Name) and dec.id == "private": private = True elif isinstance(dec, ast.Name) and dec.id == "public": public = True else: raise StructureException("Bad decorator", dec) if public and private: raise StructureException( "Cannot use public and private decorators on the same function: {}" .format(name)) # if payable and const: # raise StructureException("Function {} cannot be both constant and payable.".format(name)) # if payable and private: # raise StructureException("Function {} cannot be both private and payable.".format(name)) if (not public and not private) and not contract_def: raise StructureException( "Function visibility must be declared (@public or @private)", code) if constant: const = True # Determine the return type and whether or not it's constant. Expects something # of the form: # def foo(): ... # def foo() -> int128: ... # If there is no return type, ie. it's of the form def foo(): ... # and NOT def foo() -> type: ..., then it's null if not code.returns: output_type = None elif isinstance( code.returns, (ast.Name, ast.Compare, ast.Subscript, ast.Call, ast.Tuple)): output_type = parse_type(code.returns, None, sigs, custom_units=custom_units) else: raise InvalidTypeException( "Output type invalid or unsupported: %r" % parse_type(code.returns, None), code.returns, ) # Output type must be canonicalizable if output_type is not None: assert isinstance(output_type, TupleType) or canonicalize_type(output_type) # Get the canonical function signature sig = cls.get_full_sig(name, code.args.args, sigs, custom_units) # Take the first 4 bytes of the hash of the sig to get the method ID method_id = fourbytes_to_int(sha3(bytes(sig, 'utf-8'))[:4]) return cls(name, args, output_type, const, payable, private, sig, method_id, custom_units)
def size(self): return get_size_of_type(self.typ)
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)) 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)) 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)) 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) ) 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)) 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)) 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) ) else: raise TypeMismatchException("Can only return base type!", self.stmt)
def parse_assert(self): test_expr = Expr.parse_value_expr(self.stmt.test, self.context) if not self.is_bool_expr(test_expr): raise TypeMismatchException('Only boolean expressions allowed', self.stmt.test) if self.stmt.msg: if len(self.stmt.msg.s.strip()) == 0: raise StructureException('Empty reason string not allowed.', self.stmt) reason_str = self.stmt.msg.s.strip() sig_placeholder = self.context.new_placeholder(BaseType(32)) arg_placeholder = self.context.new_placeholder(BaseType(32)) reason_str_type = ByteArrayType(len(reason_str)) placeholder_bytes = Expr(self.stmt.msg, self.context).lll_node method_id = fourbytes_to_int(sha3(b"Error(string)")[:4]) assert_reason = \ ['seq', ['mstore', sig_placeholder, method_id], ['mstore', arg_placeholder, 32], placeholder_bytes, ['assert_reason', test_expr, int(sig_placeholder + 28), int(4 + 32 + get_size_of_type(reason_str_type) * 32)]] return LLLnode.from_list(assert_reason, typ=None, pos=getpos(self.stmt)) else: return LLLnode.from_list(['assert', test_expr], typ=None, pos=getpos(self.stmt))