Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
    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
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
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