def test_lll_from_s_expression(get_contract_from_lll):
    code = """
(seq
  (return
    0
    (lll ; just return 32 byte of calldata back
      (seq
          (calldatacopy 0 4 32)
          (return 0 32)
          stop
        )
      0)))
    """
    abi = [{
        "name": "test",
        "outputs": [{
            "type": "int128",
            "name": "out"
        }],
        "inputs": [{
            "type": "int128",
            "name": "a"
        }],
        "constant": False,
        "payable": False,
        "type": "function",
        "gas": 394
    }]

    s_expressions = parse_s_exp(code)
    lll = LLLnode.from_list(s_expressions[0])
    c = get_contract_from_lll(lll, abi=abi)
    assert c.test(-123456) == -123456
def preapproved_call_to(addr):
    return LLLnode.from_list([
        'seq',
        ['return', [0],
         ['lll',
          ['seq',
           ['calldatacopy', 0, 0, 128],
           ['call', 3000, int(addr, 16), 0, 0, 128, 0, 32],
           ['return', 0, 32]],
         [0]]]])
示例#3
0
def compile_to_assembly(code, withargs=None, existing_labels=None, break_dest=None, height=0):
    if withargs is None:
        withargs = {}
    assert isinstance(withargs, dict)

    if existing_labels is None:
        existing_labels = set()
    assert isinstance(existing_labels, set)

    # Opcodes
    if isinstance(code.value, str) and code.value.upper() in opcodes:
        o = []
        for i, c in enumerate(code.args[::-1]):
            o.extend(compile_to_assembly(c, withargs, existing_labels, break_dest, height + i))
        o.append(code.value.upper())
        return o
    # Numbers
    elif isinstance(code.value, int):
        if code.value <= -2**255:
            raise Exception("Value too low: %d" % code.value)
        elif code.value >= 2**256:
            raise Exception("Value too high: %d" % code.value)
        bytez = num_to_bytearray(code.value % 2**256) or [0]
        return ['PUSH' + str(len(bytez))] + bytez
    # Variables connected to with statements
    elif isinstance(code.value, str) and code.value in withargs:
        if height - withargs[code.value] > 16:
            raise Exception("With statement too deep")
        return ['DUP' + str(height - withargs[code.value])]
    # Setting variables connected to with statements
    elif code.value == "set":
        if len(code.args) != 2 or code.args[0].value not in withargs:
            raise Exception("Set expects two arguments, the first being a stack variable")
        if height - withargs[code.args[0].value] > 16:
            raise Exception("With statement too deep")
        return compile_to_assembly(code.args[1], withargs, existing_labels, break_dest, height) + \
            ['SWAP' + str(height - withargs[code.args[0].value]), 'POP']
    # Pass statements
    elif code.value == 'pass':
        return []
    # Code length
    elif code.value == '~codelen':
        return ['_sym_codeend']
    # Calldataload equivalent for code
    elif code.value == 'codeload':
        return compile_to_assembly(LLLnode.from_list(
            [
                'seq',
                ['codecopy', MemoryPositions.FREE_VAR_SPACE, code.args[0], 32],
                ['mload', MemoryPositions.FREE_VAR_SPACE]
            ]
        ), withargs, break_dest, height)
    # If statements (2 arguments, ie. if x: y)
    elif code.value in ('if', 'if_unchecked') and len(code.args) == 2:
        o = []
        o.extend(compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height))
        end_symbol = mksymbol()
        o.extend(['ISZERO', end_symbol, 'JUMPI'])
        o.extend(compile_to_assembly(code.args[1], withargs, existing_labels, break_dest, height))
        o.extend([end_symbol, 'JUMPDEST'])
        return o
    # If statements (3 arguments, ie. if x: y, else: z)
    elif code.value == 'if' and len(code.args) == 3:
        o = []
        o.extend(compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height))
        mid_symbol = mksymbol()
        end_symbol = mksymbol()
        o.extend(['ISZERO', mid_symbol, 'JUMPI'])
        o.extend(compile_to_assembly(code.args[1], withargs, existing_labels, break_dest, height))
        o.extend([end_symbol, 'JUMP', mid_symbol, 'JUMPDEST'])
        o.extend(compile_to_assembly(code.args[2], withargs, existing_labels, break_dest, height))
        o.extend([end_symbol, 'JUMPDEST'])
        return o
    # Repeat statements (compiled from for loops)
    # Repeat(memloc, start, rounds, body)
    elif code.value == 'repeat':
        o = []
        loops = num_to_bytearray(code.args[2].value)
        start, continue_dest, end = mksymbol(), mksymbol(), mksymbol()
        o.extend(compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height))
        o.extend(compile_to_assembly(
            code.args[1],
            withargs,
            existing_labels,
            break_dest,
            height + 1,
        ))
        o.extend(['PUSH' + str(len(loops))] + loops)
        # stack: memloc, startvalue, rounds
        o.extend(['DUP2', 'DUP4', 'MSTORE', 'ADD', start, 'JUMPDEST'])
        # stack: memloc, exit_index
        o.extend(compile_to_assembly(
            code.args[3],
            withargs,
            existing_labels,
            (end, continue_dest, height + 2),
            height + 2,
        ))
        # stack: memloc, exit_index
        o.extend([
            continue_dest, 'JUMPDEST', 'DUP2', 'MLOAD', 'PUSH1', 1, 'ADD', 'DUP1', 'DUP4', 'MSTORE',
        ])
        # stack: len(loops), index memory address, new index
        o.extend(['DUP2', 'EQ', 'ISZERO', start, 'JUMPI', end, 'JUMPDEST', 'POP', 'POP'])
        return o
    # Continue to the next iteration of the for loop
    elif code.value == 'continue':
        if not break_dest:
            raise Exception("Invalid break")
        dest, continue_dest, break_height = break_dest
        return [continue_dest, 'JUMP']
    # Break from inside a for loop
    elif code.value == 'break':
        if not break_dest:
            raise Exception("Invalid break")
        dest, continue_dest, break_height = break_dest
        return ['POP'] * (height - break_height) + [dest, 'JUMP']
    # With statements
    elif code.value == 'with':
        o = []
        o.extend(compile_to_assembly(code.args[1], withargs, existing_labels, break_dest, height))
        old = withargs.get(code.args[0].value, None)
        withargs[code.args[0].value] = height
        o.extend(compile_to_assembly(
            code.args[2],
            withargs,
            existing_labels,
            break_dest,
            height + 1,
        ))
        if code.args[2].valency:
            o.extend(['SWAP1', 'POP'])
        else:
            o.extend(['POP'])
        if old is not None:
            withargs[code.args[0].value] = old
        else:
            del withargs[code.args[0].value]
        return o
    # LLL statement (used to contain code inside code)
    elif code.value == 'lll':
        o = []
        begincode = mksymbol()
        endcode = mksymbol()
        o.extend([endcode, 'JUMP', begincode, 'BLANK'])
        # The `append(...)` call here is intentional
        o.append(compile_to_assembly(code.args[0], {}, existing_labels, None, 0))
        o.extend([endcode, 'JUMPDEST', begincode, endcode, 'SUB', begincode])
        o.extend(compile_to_assembly(code.args[1], withargs, existing_labels, break_dest, height))
        o.extend(['CODECOPY', begincode, endcode, 'SUB'])
        return o
    # Seq (used to piece together multiple statements)
    elif code.value == 'seq':
        o = []
        for arg in code.args:
            o.extend(compile_to_assembly(arg, withargs, existing_labels, break_dest, height))
            if arg.valency == 1 and arg != code.args[-1]:
                o.append('POP')
        return o
    # Seq without popping.
    elif code.value == 'seq_unchecked':
        o = []
        for arg in code.args:
            o.extend(compile_to_assembly(arg, withargs, existing_labels, break_dest, height))
            # if arg.valency == 1 and arg != code.args[-1]:
            #     o.append('POP')
        return o
    # Assure (if false, invalid opcode)
    elif code.value == 'assert_unreachable':
        o = compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height)
        end_symbol = mksymbol()
        o.extend([
            end_symbol,
            'JUMPI',
            'INVALID',
            end_symbol,
            'JUMPDEST'
        ])
        return o
    # Assert (if false, exit)
    elif code.value == 'assert':
        o = compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height)
        o.extend(get_revert())
        return o
    elif code.value == 'assert_reason':
        o = compile_to_assembly(code.args[0], withargs, break_dest, height)
        mem_start = compile_to_assembly(code.args[1], withargs, break_dest, height)
        mem_len = compile_to_assembly(code.args[2], withargs, break_dest, height)
        o.extend(get_revert(mem_start, mem_len))
        return o
    # Unsigned/signed clamp, check less-than
    elif code.value in CLAMP_OP_NAMES:
        if isinstance(code.args[0].value, int) and isinstance(code.args[1].value, int):
            # Checks for clamp errors at compile time as opposed to run time
            args_0_val = code.args[0].value
            args_1_val = code.args[1].value
            is_free_of_clamp_errors = any((
                code.value in ('uclamplt', 'clamplt') and 0 <= args_0_val < args_1_val,
                code.value in ('uclample', 'clample') and 0 <= args_0_val <= args_1_val,
                code.value in ('uclampgt', 'clampgt') and 0 <= args_0_val > args_1_val,
                code.value in ('uclampge', 'clampge') and 0 <= args_0_val >= args_1_val,
            ))
            if is_free_of_clamp_errors:
                return compile_to_assembly(
                    code.args[0], withargs, existing_labels, break_dest, height,
                )
            else:
                raise Exception(
                    "Invalid %r with values %r and %r" % (code.value, code.args[0], code.args[1])
                )
        o = compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height)
        o.extend(compile_to_assembly(
            code.args[1], withargs, existing_labels, break_dest, height + 1,
        ))
        o.extend(['DUP2'])
        # Stack: num num bound
        if code.value == 'uclamplt':
            o.extend(['LT'])
        elif code.value == "clamplt":
            o.extend(['SLT'])
        elif code.value == "uclample":
            o.extend(['GT', 'ISZERO'])
        elif code.value == "clample":
            o.extend(['SGT', 'ISZERO'])
        elif code.value == 'uclampgt':
            o.extend(['GT'])
        elif code.value == "clampgt":
            o.extend(['SGT'])
        elif code.value == "uclampge":
            o.extend(['LT', 'ISZERO'])
        elif code.value == "clampge":
            o.extend(['SLT', 'ISZERO'])
        o.extend(get_revert())
        return o
    # Signed clamp, check against upper and lower bounds
    elif code.value in ('clamp', 'uclamp'):
        comp1 = 'SGT' if code.value == 'clamp' else 'GT'
        comp2 = 'SLT' if code.value == 'clamp' else 'LT'
        o = compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height)
        o.extend(compile_to_assembly(
            code.args[1], withargs, existing_labels, break_dest, height + 1,
        ))
        o.extend(['DUP1'])
        o.extend(compile_to_assembly(
            code.args[2], withargs, existing_labels, break_dest, height + 3,
        ))
        o.extend(['SWAP1', comp1, 'ISZERO'])
        o.extend(get_revert())
        o.extend(['DUP1', 'SWAP2', 'SWAP1', comp2, 'ISZERO'])
        o.extend(get_revert())
        return o
    # Checks that a value is nonzero
    elif code.value == 'clamp_nonzero':
        o = compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height)
        o.extend(['DUP1'])
        o.extend(get_revert())
        return o
    # SHA3 a single value
    elif code.value == 'sha3_32':
        o = compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height)
        o.extend([
            'PUSH1', MemoryPositions.FREE_VAR_SPACE,
            'MSTORE',
            'PUSH1', 32,
            'PUSH1', MemoryPositions.FREE_VAR_SPACE,
            'SHA3'
        ])
        return o
    # SHA3 a 64 byte value
    elif code.value == 'sha3_64':
        o = compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height)
        o.extend(compile_to_assembly(code.args[1], withargs, existing_labels, break_dest, height))
        o.extend([
            'PUSH1', MemoryPositions.FREE_VAR_SPACE2,
            'MSTORE',
            'PUSH1', MemoryPositions.FREE_VAR_SPACE,
            'MSTORE',
            'PUSH1', 64,
            'PUSH1', MemoryPositions.FREE_VAR_SPACE,
            'SHA3'
        ])
        return o
    # <= operator
    elif code.value == 'le':
        return compile_to_assembly(LLLnode.from_list(
            [
                'iszero',
                ['gt', code.args[0], code.args[1]],
            ]
        ), withargs, existing_labels, break_dest, height)
    # >= operator
    elif code.value == 'ge':
        return compile_to_assembly(LLLnode.from_list(
            [
                'iszero',
                ['lt', code.args[0], code.args[1]],
            ]
        ), withargs, existing_labels, break_dest, height)
    # <= operator
    elif code.value == 'sle':
        return compile_to_assembly(LLLnode.from_list(
            [
                'iszero',
                ['sgt', code.args[0], code.args[1]],
            ]
        ), withargs, existing_labels, break_dest, height)
    # >= operator
    elif code.value == 'sge':
        return compile_to_assembly(LLLnode.from_list(
            [
                'iszero',
                ['slt', code.args[0], code.args[1]],
            ]
        ), withargs, existing_labels, break_dest, height)
    # != operator
    elif code.value == 'ne':
        return compile_to_assembly(LLLnode.from_list(
            [
                'iszero',
                ['eq', code.args[0], code.args[1]],
            ]
        ), withargs, existing_labels, break_dest, height)
    # e.g. 95 -> 96, 96 -> 96, 97 -> 128
    elif code.value == "ceil32":
        return compile_to_assembly(LLLnode.from_list(
            [
                'with', '_val', code.args[0],
                [
                    'sub',
                    ['add', '_val', 31],
                    ['mod', ['sub', '_val', 1], 32],
                ]
            ]
        ), withargs, existing_labels, break_dest, height)
    # # jump to a symbol
    elif code.value == 'goto':
        return [
            '_sym_' + str(code.args[0]),
            'JUMP'
        ]
    elif isinstance(code.value, str) and code.value.startswith('_sym_'):
        return code.value
    # set a symbol as a location.
    elif code.value == 'label':
        label_name = str(code.args[0])

        if label_name in existing_labels:
            raise Exception('Label with name %s already exists!', label_name)
        else:
            existing_labels.add(label_name)

        return [
            '_sym_' + label_name,
            'JUMPDEST'
        ]
    # inject debug opcode.
    elif code.value == 'debugger':
        return mkdebug(pc_debugger=False, pos=code.pos)
    # inject debug opcode.
    elif code.value == 'pc_debugger':
        return mkdebug(pc_debugger=True, pos=code.pos)
    else:
        raise Exception("Weird code element: " + repr(code))
示例#4
0
def test_lll_compile_fail(lll):
    optimized = optimizer.optimize(LLLnode.from_list(lll[0]))
    optimized.repr_show_gas = True
    hand_optimized = LLLnode.from_list(lll[1])
    hand_optimized.repr_show_gas = True
    assert optimized == hand_optimized
示例#5
0
def compile_to_assembly(code, withargs=None, break_dest=None, height=0):
    if withargs is None:
        withargs = {}

    # Opcodes
    if isinstance(code.value, str) and code.value.upper() in opcodes:
        o = []
        for i, c in enumerate(code.args[::-1]):
            o.extend(compile_to_assembly(c, withargs, break_dest, height + i))
        o.append(code.value.upper())
        return o
    # Numbers
    elif isinstance(code.value, int):
        if code.value <= -2**255:
            raise Exception("Value too low: %d" % code.value)
        elif code.value >= 2**256:
            raise Exception("Value too high: %d" % code.value)
        bytez = num_to_bytearray(code.value % 2**256) or [0]
        return ['PUSH' + str(len(bytez))] + bytez
    # Variables connected to with statements
    elif isinstance(code.value, str) and code.value in withargs:
        if height - withargs[code.value] > 16:
            raise Exception("With statement too deep")
        return ['DUP' + str(height - withargs[code.value])]
    # Setting variables connected to with statements
    elif code.value == "set":
        if height - withargs[code.args[0].value] > 16:
            raise Exception("With statement too deep")
        if len(code.args) != 2 or code.args[0].value not in withargs:
            raise Exception("Set expects two arguments, the first being a stack variable")
        return compile_to_assembly(code.args[1], withargs, break_dest, height) + \
            ['SWAP' + str(height - withargs[code.args[0].value]), 'POP']
    # Pass statements
    elif code.value == 'pass':
        return []
    # Code length
    elif code.value == '~codelen':
        return ['_sym_codeend']
    # Calldataload equivalent for code
    elif code.value == 'codeload':
        return compile_to_assembly(LLLnode.from_list(['seq', ['codecopy', MemoryPositions.FREE_VAR_SPACE, code.args[0], 32], ['mload', MemoryPositions.FREE_VAR_SPACE]]),
                                   withargs, break_dest, height)
    # If statements (2 arguments, ie. if x: y)
    elif code.value == 'if' and len(code.args) == 2:
        o = []
        o.extend(compile_to_assembly(code.args[0], withargs, break_dest, height))
        end_symbol = mksymbol()
        o.extend(['ISZERO', end_symbol, 'JUMPI'])
        o.extend(compile_to_assembly(code.args[1], withargs, break_dest, height))
        o.extend([end_symbol, 'JUMPDEST'])
        return o
    # If statements (3 arguments, ie. if x: y, else: z)
    elif code.value == 'if' and len(code.args) == 3:
        o = []
        o.extend(compile_to_assembly(code.args[0], withargs, break_dest, height))
        mid_symbol = mksymbol()
        end_symbol = mksymbol()
        o.extend(['ISZERO', mid_symbol, 'JUMPI'])
        o.extend(compile_to_assembly(code.args[1], withargs, break_dest, height))
        o.extend([end_symbol, 'JUMP', mid_symbol, 'JUMPDEST'])
        o.extend(compile_to_assembly(code.args[2], withargs, break_dest, height))
        o.extend([end_symbol, 'JUMPDEST'])
        return o
    # Repeat statements (compiled from for loops)
    # Repeat(memloc, start, rounds, body)
    elif code.value == 'repeat':
        o = []
        loops = num_to_bytearray(code.args[2].value)
        if not loops:
            raise Exception("Number of times repeated must be a constant nonzero positive integer: %r" % loops)
        start, continue_dest, end = mksymbol(), mksymbol(), mksymbol()
        o.extend(compile_to_assembly(code.args[0], withargs, break_dest, height))
        o.extend(compile_to_assembly(code.args[1], withargs, break_dest, height + 1))
        o.extend(['PUSH' + str(len(loops))] + loops)
        # stack: memloc, startvalue, rounds
        o.extend(['DUP2', 'DUP4', 'MSTORE', 'ADD', start, 'JUMPDEST'])
        # stack: memloc, exit_index
        o.extend(compile_to_assembly(code.args[3], withargs, (end, continue_dest, height + 2), height + 2))
        # stack: memloc, exit_index
        o.extend([continue_dest, 'JUMPDEST', 'DUP2', 'MLOAD', 'PUSH1', 1, 'ADD', 'DUP1', 'DUP4', 'MSTORE'])
        # stack: len(loops), index memory address, new index
        o.extend(['DUP2', 'EQ', 'ISZERO', start, 'JUMPI', end, 'JUMPDEST', 'POP', 'POP'])
        return o
    # Continue to the next iteration of the for loop
    elif code.value == 'continue':
        if not break_dest:
            raise Exception("Invalid break")
        dest, continue_dest, break_height = break_dest
        return [continue_dest, 'JUMP']
    # Break from inside a for loop
    elif code.value == 'break':
        if not break_dest:
            raise Exception("Invalid break")
        dest, continue_dest, break_height = break_dest
        return ['POP'] * (height - break_height) + [dest, 'JUMP']
    # With statements
    elif code.value == 'with':
        o = []
        o.extend(compile_to_assembly(code.args[1], withargs, break_dest, height))
        old = withargs.get(code.args[0].value, None)
        withargs[code.args[0].value] = height
        o.extend(compile_to_assembly(code.args[2], withargs, break_dest, height + 1))
        if code.args[2].valency:
            o.extend(['SWAP1', 'POP'])
        else:
            o.extend(['POP'])
        if old is not None:
            withargs[code.args[0].value] = old
        else:
            del withargs[code.args[0].value]
        return o
    # LLL statement (used to contain code inside code)
    elif code.value == 'lll':
        o = []
        begincode = mksymbol()
        endcode = mksymbol()
        o.extend([endcode, 'JUMP', begincode, 'BLANK'])
        o.append(compile_to_assembly(code.args[0], {}, None, 0))  # Append is intentional
        o.extend([endcode, 'JUMPDEST', begincode, endcode, 'SUB', begincode])
        o.extend(compile_to_assembly(code.args[1], withargs, break_dest, height))
        o.extend(['CODECOPY', begincode, endcode, 'SUB'])
        return o
    # Seq (used to piece together multiple statements)
    elif code.value == 'seq':
        o = []
        for arg in code.args:
            o.extend(compile_to_assembly(arg, withargs, break_dest, height))
            if arg.valency == 1 and arg != code.args[-1]:
                o.append('POP')
        return o
    # Assert (if false, exit)
    elif code.value == 'assert':
        o = compile_to_assembly(code.args[0], withargs, break_dest, height)
        o.extend(get_revert())
        return o
    # Unsigned/signed clamp, check less-than
    elif code.value in ('uclamplt', 'uclample', 'clamplt', 'clample', 'uclampgt', 'uclampge', 'clampgt', 'clampge'):
        if isinstance(code.args[0].value, int) and isinstance(code.args[1].value, int):
            # Checks for clamp errors at compile time as opposed to run time
            if code.value in ('uclamplt', 'clamplt') and 0 <= code.args[0].value < code.args[1].value or \
            code.value in ('uclample', 'clample') and 0 <= code.args[0].value <= code.args[1].value or \
            code.value in ('uclampgt', 'clampgt') and 0 <= code.args[0].value > code.args[1].value or \
            code.value in ('uclampge', 'clampge') and 0 <= code.args[0].value >= code.args[1].value:
                return compile_to_assembly(code.args[0], withargs, break_dest, height)
            else:
                raise Exception("Invalid %r with values %r and %r" % (code.value, code.args[0], code.args[1]))
        o = compile_to_assembly(code.args[0], withargs, break_dest, height)
        o.extend(compile_to_assembly(code.args[1], withargs, break_dest, height + 1))
        o.extend(['DUP2'])
        # Stack: num num bound
        if code.value == 'uclamplt':
            o.extend(['LT'])
        elif code.value == "clamplt":
            o.extend(['SLT'])
        elif code.value == "uclample":
            o.extend(['GT', 'ISZERO'])
        elif code.value == "clample":
            o.extend(['SGT', 'ISZERO'])
        elif code.value == 'uclampgt':
            o.extend(['GT'])
        elif code.value == "clampgt":
            o.extend(['SGT'])
        elif code.value == "uclampge":
            o.extend(['LT', 'ISZERO'])
        elif code.value == "clampge":
            o.extend(['SLT', 'ISZERO'])
        o.extend(get_revert())
        return o
    # Signed clamp, check against upper and lower bounds
    elif code.value in ('clamp', 'uclamp'):
        comp1 = 'SGT' if code.value == 'clamp' else 'GT'
        comp2 = 'SLT' if code.value == 'clamp' else 'LT'
        o = compile_to_assembly(code.args[0], withargs, break_dest, height)
        o.extend(compile_to_assembly(code.args[1], withargs, break_dest, height + 1))
        o.extend(['DUP1'])
        o.extend(compile_to_assembly(code.args[2], withargs, break_dest, height + 3))
        o.extend(['SWAP1', comp1, 'PC', 'JUMPI'])
        o.extend(['DUP1', 'SWAP2', 'SWAP1', comp2, 'ISZERO'])
        o.extend(get_revert())
        return o
    # Checks that a value is nonzero
    elif code.value == 'clamp_nonzero':
        o = compile_to_assembly(code.args[0], withargs, break_dest, height)
        o.extend(['DUP1'])
        o.extend(get_revert())
        return o
    # SHA3 a single value
    elif code.value == 'sha3_32':
        o = compile_to_assembly(code.args[0], withargs, break_dest, height)
        o.extend(['PUSH1', MemoryPositions.FREE_VAR_SPACE, 'MSTORE', 'PUSH1', 32, 'PUSH1', MemoryPositions.FREE_VAR_SPACE, 'SHA3'])
        return o
    # <= operator
    elif code.value == 'le':
        return compile_to_assembly(LLLnode.from_list(['iszero', ['gt', code.args[0], code.args[1]]]), withargs, break_dest, height)
    # >= operator
    elif code.value == 'ge':
        return compile_to_assembly(LLLnode.from_list(['iszero', ['lt', code.args[0], code.args[1]]]), withargs, break_dest, height)
    # <= operator
    elif code.value == 'sle':
        return compile_to_assembly(LLLnode.from_list(['iszero', ['sgt', code.args[0], code.args[1]]]), withargs, break_dest, height)
    # >= operator
    elif code.value == 'sge':
        return compile_to_assembly(LLLnode.from_list(['iszero', ['slt', code.args[0], code.args[1]]]), withargs, break_dest, height)
    # != operator
    elif code.value == 'ne':
        return compile_to_assembly(LLLnode.from_list(['iszero', ['eq', code.args[0], code.args[1]]]), withargs, break_dest, height)
    # eg. 95 -> 96, 96 -> 96, 97 -> 128
    elif code.value == "ceil32":
        return compile_to_assembly(LLLnode.from_list(['with', '_val', code.args[0],
                                                        ['sub', ['add', '_val', 31],
                                                                ['mod', ['sub', '_val', 1], 32]]]), withargs, break_dest, height)
    else:
        raise Exception("Weird code element: " + repr(code))
def foo() -> uint256:
    return block.number
""",
"""
@public
def foo() -> uint256:
    return msg.gas
"""]

ecrecover_lll_src = LLLnode.from_list([
    'seq',
    ['return', [0],
     ['lll',
      ['seq',
       ['calldatacopy', 0, 0, 128],
       ['call', 3000, 1, 0, 0, 128, 0, 32],
       ['mstore',
        0,
        ['eq',
         ['mload', 0],
         0]],
       ['return', 0, 32]],
      [0]]]])

def preapproved_call_to(addr):
    return LLLnode.from_list([
        'seq',
        ['return', [0],
         ['lll',
          ['seq',
           ['calldatacopy', 0, 0, 128],
           ['call', 3000, int(addr, 16), 0, 0, 128, 0, 32],
def test_compile_lll_good(good_lll, get_contract_from_lll):
    get_contract_from_lll(LLLnode.from_list(good_lll))
def test_lll_compile_fail(bad_lll, get_contract_from_lll,
                          assert_compile_failed):
    assert_compile_failed(
        lambda: get_contract_from_lll(LLLnode.from_list(bad_lll)), Exception)
示例#9
0
def compile_to_assembly(code,
                        withargs=None,
                        existing_labels=None,
                        break_dest=None,
                        height=0):
    if withargs is None:
        withargs = {}
    if not isinstance(withargs, dict):
        raise CompilerPanic(f"Incorrect type for withargs: {type(withargs)}")

    if existing_labels is None:
        existing_labels = set()
    if not isinstance(existing_labels, set):
        raise CompilerPanic(
            f"Incorrect type for existing_labels: {type(existing_labels)}")

    # Opcodes
    if isinstance(code.value, str) and code.value.upper() in get_opcodes():
        o = []
        for i, c in enumerate(code.args[::-1]):
            o.extend(
                compile_to_assembly(c, withargs, existing_labels, break_dest,
                                    height + i))
        o.append(code.value.upper())
        return o
    # Numbers
    elif isinstance(code.value, int):
        if code.value <= -(2**255):
            raise Exception(f"Value too low: {code.value}")
        elif code.value >= 2**256:
            raise Exception(f"Value too high: {code.value}")
        bytez = num_to_bytearray(code.value % 2**256) or [0]
        return ["PUSH" + str(len(bytez))] + bytez
    # Variables connected to with statements
    elif isinstance(code.value, str) and code.value in withargs:
        if height - withargs[code.value] > 16:
            raise Exception("With statement too deep")
        return ["DUP" + str(height - withargs[code.value])]
    # Setting variables connected to with statements
    elif code.value == "set":
        if len(code.args) != 2 or code.args[0].value not in withargs:
            raise Exception(
                "Set expects two arguments, the first being a stack variable")
        if height - withargs[code.args[0].value] > 16:
            raise Exception("With statement too deep")
        return compile_to_assembly(
            code.args[1], withargs, existing_labels, break_dest, height) + [
                "SWAP" + str(height - withargs[code.args[0].value]),
                "POP",
            ]
    # Pass statements
    elif code.value == "pass":
        return []
    # Code length
    elif code.value == "~codelen":
        return ["_sym_codeend"]
    # Calldataload equivalent for code
    elif code.value == "codeload":
        return compile_to_assembly(
            LLLnode.from_list([
                "seq",
                ["codecopy", MemoryPositions.FREE_VAR_SPACE, code.args[0], 32],
                ["mload", MemoryPositions.FREE_VAR_SPACE],
            ]),
            withargs,
            existing_labels,
            break_dest,
            height,
        )
    # If statements (2 arguments, ie. if x: y)
    elif code.value in ("if", "if_unchecked") and len(code.args) == 2:
        o = []
        o.extend(
            compile_to_assembly(code.args[0], withargs, existing_labels,
                                break_dest, height))
        end_symbol = mksymbol()
        o.extend(["ISZERO", end_symbol, "JUMPI"])
        o.extend(
            compile_to_assembly(code.args[1], withargs, existing_labels,
                                break_dest, height))
        o.extend([end_symbol, "JUMPDEST"])
        return o
    # If statements (3 arguments, ie. if x: y, else: z)
    elif code.value == "if" and len(code.args) == 3:
        o = []
        o.extend(
            compile_to_assembly(code.args[0], withargs, existing_labels,
                                break_dest, height))
        mid_symbol = mksymbol()
        end_symbol = mksymbol()
        o.extend(["ISZERO", mid_symbol, "JUMPI"])
        o.extend(
            compile_to_assembly(code.args[1], withargs, existing_labels,
                                break_dest, height))
        o.extend([end_symbol, "JUMP", mid_symbol, "JUMPDEST"])
        o.extend(
            compile_to_assembly(code.args[2], withargs, existing_labels,
                                break_dest, height))
        o.extend([end_symbol, "JUMPDEST"])
        return o
    # Repeat statements (compiled from for loops)
    # Repeat(memloc, start, rounds, body)
    elif code.value == "repeat":
        o = []
        loops = num_to_bytearray(code.args[2].value)
        start, continue_dest, end = mksymbol(), mksymbol(), mksymbol()
        o.extend(
            compile_to_assembly(code.args[0], withargs, existing_labels,
                                break_dest, height))
        o.extend(
            compile_to_assembly(
                code.args[1],
                withargs,
                existing_labels,
                break_dest,
                height + 1,
            ))
        o.extend(["PUSH" + str(len(loops))] + loops)
        # stack: memloc, startvalue, rounds
        o.extend(["DUP2", "DUP4", "MSTORE", "ADD", start, "JUMPDEST"])
        # stack: memloc, exit_index
        o.extend(
            compile_to_assembly(
                code.args[3],
                withargs,
                existing_labels,
                (end, continue_dest, height + 2),
                height + 2,
            ))
        # stack: memloc, exit_index
        o.extend([
            continue_dest,
            "JUMPDEST",
            "DUP2",
            "MLOAD",
            "PUSH1",
            1,
            "ADD",
            "DUP1",
            "DUP4",
            "MSTORE",
        ])
        # stack: len(loops), index memory address, new index
        o.extend([
            "DUP2", "EQ", "ISZERO", start, "JUMPI", end, "JUMPDEST", "POP",
            "POP"
        ])
        return o
    # Continue to the next iteration of the for loop
    elif code.value == "continue":
        if not break_dest:
            raise CompilerPanic("Invalid break")
        dest, continue_dest, break_height = break_dest
        return [continue_dest, "JUMP"]
    # Break from inside a for loop
    elif code.value == "break":
        if not break_dest:
            raise CompilerPanic("Invalid break")
        dest, continue_dest, break_height = break_dest
        return ["POP"] * (height - break_height) + [dest, "JUMP"]
    # Break from inside one or more for loops prior to a return statement inside the loop
    elif code.value == "exit_repeater":
        if not break_dest:
            raise CompilerPanic("Invalid break")
        _, _, break_height = break_dest
        return ["POP"] * break_height
    # With statements
    elif code.value == "with":
        o = []
        o.extend(
            compile_to_assembly(code.args[1], withargs, existing_labels,
                                break_dest, height))
        old = withargs.get(code.args[0].value, None)
        withargs[code.args[0].value] = height
        o.extend(
            compile_to_assembly(
                code.args[2],
                withargs,
                existing_labels,
                break_dest,
                height + 1,
            ))
        if code.args[2].valency:
            o.extend(["SWAP1", "POP"])
        else:
            o.extend(["POP"])
        if old is not None:
            withargs[code.args[0].value] = old
        else:
            del withargs[code.args[0].value]
        return o
    # LLL statement (used to contain code inside code)
    elif code.value == "lll":
        o = []
        begincode = mksymbol()
        endcode = mksymbol()
        o.extend([endcode, "JUMP", begincode, "BLANK"])
        # The `append(...)` call here is intentional
        o.append(
            compile_to_assembly(code.args[0], {}, existing_labels, None, 0))
        o.extend([endcode, "JUMPDEST", begincode, endcode, "SUB", begincode])
        o.extend(
            compile_to_assembly(code.args[1], withargs, existing_labels,
                                break_dest, height))
        o.extend(["CODECOPY", begincode, endcode, "SUB"])
        return o
    # Seq (used to piece together multiple statements)
    elif code.value == "seq":
        o = []
        for arg in code.args:
            o.extend(
                compile_to_assembly(arg, withargs, existing_labels, break_dest,
                                    height))
            if arg.valency == 1 and arg != code.args[-1]:
                o.append("POP")
        return o
    # Seq without popping.
    elif code.value == "seq_unchecked":
        o = []
        for arg in code.args:
            o.extend(
                compile_to_assembly(arg, withargs, existing_labels, break_dest,
                                    height))
            # if arg.valency == 1 and arg != code.args[-1]:
            #     o.append('POP')
        return o
    # Assure (if false, invalid opcode)
    elif code.value == "assert_unreachable":
        o = compile_to_assembly(code.args[0], withargs, existing_labels,
                                break_dest, height)
        end_symbol = mksymbol()
        o.extend([end_symbol, "JUMPI", "INVALID", end_symbol, "JUMPDEST"])
        return o
    # Assert (if false, exit)
    elif code.value == "assert":
        o = compile_to_assembly(code.args[0], withargs, existing_labels,
                                break_dest, height)
        o.extend(get_revert())
        return o
    elif code.value == "assert_reason":
        o = compile_to_assembly(code.args[0], withargs, existing_labels,
                                break_dest, height)
        mem_start = compile_to_assembly(code.args[1], withargs,
                                        existing_labels, break_dest, height)
        mem_len = compile_to_assembly(code.args[2], withargs, existing_labels,
                                      break_dest, height)
        o.extend(get_revert(mem_start, mem_len))
        return o
    # Unsigned/signed clamp, check less-than
    elif code.value in CLAMP_OP_NAMES:
        if isinstance(code.args[0].value, int) and isinstance(
                code.args[1].value, int):
            # Checks for clamp errors at compile time as opposed to run time
            args_0_val = code.args[0].value
            args_1_val = code.args[1].value
            is_free_of_clamp_errors = any((
                code.value in ("uclamplt", "clamplt")
                and 0 <= args_0_val < args_1_val,
                code.value in ("uclample", "clample")
                and 0 <= args_0_val <= args_1_val,
                code.value in ("uclampgt", "clampgt")
                and 0 <= args_0_val > args_1_val,
                code.value in ("uclampge", "clampge")
                and 0 <= args_0_val >= args_1_val,
            ))
            if is_free_of_clamp_errors:
                return compile_to_assembly(
                    code.args[0],
                    withargs,
                    existing_labels,
                    break_dest,
                    height,
                )
            else:
                raise Exception(
                    f"Invalid {code.value} with values {code.args[0]} and {code.args[1]}"
                )
        o = compile_to_assembly(code.args[0], withargs, existing_labels,
                                break_dest, height)
        o.extend(
            compile_to_assembly(
                code.args[1],
                withargs,
                existing_labels,
                break_dest,
                height + 1,
            ))
        o.extend(["DUP2"])
        # Stack: num num bound
        if code.value == "uclamplt":
            o.extend(["LT"])
        elif code.value == "clamplt":
            o.extend(["SLT"])
        elif code.value == "uclample":
            o.extend(["GT", "ISZERO"])
        elif code.value == "clample":
            o.extend(["SGT", "ISZERO"])
        elif code.value == "uclampgt":
            o.extend(["GT"])
        elif code.value == "clampgt":
            o.extend(["SGT"])
        elif code.value == "uclampge":
            o.extend(["LT", "ISZERO"])
        elif code.value == "clampge":
            o.extend(["SLT", "ISZERO"])
        o.extend(get_revert())
        return o
    # Signed clamp, check against upper and lower bounds
    elif code.value in ("clamp", "uclamp"):
        comp1 = "SGT" if code.value == "clamp" else "GT"
        comp2 = "SLT" if code.value == "clamp" else "LT"
        o = compile_to_assembly(code.args[0], withargs, existing_labels,
                                break_dest, height)
        o.extend(
            compile_to_assembly(
                code.args[1],
                withargs,
                existing_labels,
                break_dest,
                height + 1,
            ))
        o.extend(["DUP1"])
        o.extend(
            compile_to_assembly(
                code.args[2],
                withargs,
                existing_labels,
                break_dest,
                height + 3,
            ))
        o.extend(["SWAP1", comp1, "ISZERO"])
        o.extend(get_revert())
        o.extend(["DUP1", "SWAP2", "SWAP1", comp2, "ISZERO"])
        o.extend(get_revert())
        return o
    # Checks that a value is nonzero
    elif code.value == "clamp_nonzero":
        o = compile_to_assembly(code.args[0], withargs, existing_labels,
                                break_dest, height)
        o.extend(["DUP1"])
        o.extend(get_revert())
        return o
    # SHA3 a single value
    elif code.value == "sha3_32":
        o = compile_to_assembly(code.args[0], withargs, existing_labels,
                                break_dest, height)
        o.extend([
            "PUSH1",
            MemoryPositions.FREE_VAR_SPACE,
            "MSTORE",
            "PUSH1",
            32,
            "PUSH1",
            MemoryPositions.FREE_VAR_SPACE,
            "SHA3",
        ])
        return o
    # SHA3 a 64 byte value
    elif code.value == "sha3_64":
        o = compile_to_assembly(code.args[0], withargs, existing_labels,
                                break_dest, height)
        o.extend(
            compile_to_assembly(code.args[1], withargs, existing_labels,
                                break_dest, height))
        o.extend([
            "PUSH1",
            MemoryPositions.FREE_VAR_SPACE2,
            "MSTORE",
            "PUSH1",
            MemoryPositions.FREE_VAR_SPACE,
            "MSTORE",
            "PUSH1",
            64,
            "PUSH1",
            MemoryPositions.FREE_VAR_SPACE,
            "SHA3",
        ])
        return o
    # <= operator
    elif code.value == "le":
        return compile_to_assembly(
            LLLnode.from_list(["iszero", ["gt", code.args[0], code.args[1]]]),
            withargs,
            existing_labels,
            break_dest,
            height,
        )
    # >= operator
    elif code.value == "ge":
        return compile_to_assembly(
            LLLnode.from_list(["iszero", ["lt", code.args[0], code.args[1]]]),
            withargs,
            existing_labels,
            break_dest,
            height,
        )
    # <= operator
    elif code.value == "sle":
        return compile_to_assembly(
            LLLnode.from_list(["iszero", ["sgt", code.args[0], code.args[1]]]),
            withargs,
            existing_labels,
            break_dest,
            height,
        )
    # >= operator
    elif code.value == "sge":
        return compile_to_assembly(
            LLLnode.from_list(["iszero", ["slt", code.args[0], code.args[1]]]),
            withargs,
            existing_labels,
            break_dest,
            height,
        )
    # != operator
    elif code.value == "ne":
        return compile_to_assembly(
            LLLnode.from_list(["iszero", ["eq", code.args[0], code.args[1]]]),
            withargs,
            existing_labels,
            break_dest,
            height,
        )
    # e.g. 95 -> 96, 96 -> 96, 97 -> 128
    elif code.value == "ceil32":
        return compile_to_assembly(
            LLLnode.from_list([
                "with",
                "_val",
                code.args[0],
                ["sub", ["add", "_val", 31], ["mod", ["sub", "_val", 1], 32]],
            ]),
            withargs,
            existing_labels,
            break_dest,
            height,
        )
    # # jump to a symbol
    elif code.value == "goto":
        return ["_sym_" + str(code.args[0]), "JUMP"]
    elif isinstance(code.value, str) and code.value.startswith("_sym_"):
        return code.value
    # set a symbol as a location.
    elif code.value == "label":
        label_name = str(code.args[0])

        if label_name in existing_labels:
            raise Exception(f"Label with name {label_name} already exists!")
        else:
            existing_labels.add(label_name)

        return ["_sym_" + label_name, "JUMPDEST"]
    # inject debug opcode.
    elif code.value == "debugger":
        return mkdebug(pc_debugger=False, pos=code.pos)
    # inject debug opcode.
    elif code.value == "pc_debugger":
        return mkdebug(pc_debugger=True, pos=code.pos)
    else:
        raise Exception("Weird code element: " + repr(code))
示例#10
0
def test_pc_debugger():
    debugger_lll = ["seq_unchecked", ["mstore", 0, 32], ["pc_debugger"]]
    lll_nodes = LLLnode.from_list(debugger_lll)
    _, line_number_map = compile_lll.assembly_to_evm(
        compile_lll.compile_to_assembly(lll_nodes))
    assert line_number_map["pc_breakpoints"][0] == 5
示例#11
0
purity_checker_lll = LLLnode.from_list([
    "seq",
    [
        "return",
        0,
        [
            "lll",
            [
                "seq",
                ["mstore", 28, ["calldataload", 0]],
                [
                    "mstore", 32,
                    1461501637330902918203684832716283019655932542976
                ],
                ["mstore", 64, 170141183460469231731687303715884105727],
                ["mstore", 96, -170141183460469231731687303715884105728],
                [
                    "mstore", 128,
                    1701411834604692317316873037158841057270000000000
                ],
                [
                    "mstore", 160,
                    -1701411834604692317316873037158841057280000000000
                ],
                [
                    "if",
                    ["eq", ["mload", 0], 2710585003],  # submit
                    [
                        "seq",
                        ["calldatacopy", 320, 4, 32],
                        ["assert", ["iszero", "callvalue"]],
                        ["uclamplt", ["calldataload", 4],
                         ["mload", 32]],  # checking address input
                        # scan bytecode at address input
                        [
                            "with",
                            "_EXTCODESIZE",
                            ["extcodesize", ["mload", 320]],  # addr
                            [
                                "if",
                                ["eq", "_EXTCODESIZE", 0],
                                "invalid",  # ban accounts with no code
                                [
                                    "seq",
                                    [
                                        "extcodecopy", ["mload", 320], 352, 0,
                                        "_EXTCODESIZE"
                                    ],
                                    [
                                        "with", "_i",
                                        [
                                            "add", 352,
                                            ["mul", 65, "_EXTCODESIZE"]
                                        ],
                                        [
                                            "with", "_op", 0,
                                            [
                                                "repeat", "_i", 0,
                                                115792089237316195423570985008687907853269984665640564039457584007913129639935,
                                                loop_body
                                            ]
                                        ]
                                    ]
                                ]
                            ]
                        ],
                        # approve the address `addr`
                        ["sstore", ["add", ["sha3_32", 0], ["mload", 320]], 1],
                        ["mstore", 0, 1],
                        ["return", 0, 32],
                        "stop"
                    ]
                ],
                [
                    "if",
                    ["eq", ["mload", 0], 3258357672],  # check
                    [
                        "seq",
                        ["calldatacopy", 320, 4, 32],
                        ["assert", ["iszero", "callvalue"]],
                        ["uclamplt", ["calldataload", 4],
                         ["mload", 32]],  # checking address input
                        [
                            "mstore", 0,
                            ["sload", ["add", ["sha3_32", 0], ["mload", 320]]]
                        ],
                        ["return", 0, 32],
                        "stop"
                    ]
                ]
            ],
            0
        ]
    ]
])
示例#12
0
def test_pc_debugger():
    debugger_lll = ['seq_unchecked', ['mstore', 0, 32], ['pc_debugger']]
    lll_nodes = LLLnode.from_list(debugger_lll)
    _, line_number_map = compile_lll.assembly_to_evm(
        compile_lll.compile_to_assembly(lll_nodes))
    assert line_number_map['pc_breakpoints'][0] == 5