예제 #1
0
    def disassemble(bytecode: Iterable,
                    pc: int = 0,
                    fork=pyevmasm.DEFAULT_FORK) -> str:
        ''' Disassemble an EVM bytecode

            :param bytecode: binary representation of an evm bytecode (hexadecimal)
            :param pc: program counter of the first instruction in the bytecode(optional)
            :type bytecode: str
            :return: the text representation of the assembler code

            Example use::

                >>> EVMAsm.disassemble("\x60\x60\x60\x40\x52\x60\x02\x61\x01\x00")
                ...
                PUSH1 0x60
                BLOCKHASH
                MSTORE
                PUSH1 0x2
                PUSH2 0x100

        '''
        return pyevmasm.disassemble(bytecode, pc, fork)
예제 #2
0
def gen_test(testcase, filename, skip):

    output = ''
    if skip:
        output += '''    @unittest.skip('Gas or performance related')\n'''

    testname = (os.path.split(filename)[1].replace('-','_')).split('.')[0]
    bytecode = unhexlify(testcase['exec']['code'][2:])
    disassemble = ''
    try:
        #add silent=True when evmasm supports it
        disassemble = '\n                  '.join(EVMAsm.disassemble(bytecode).split('\n'))
    except Exception as e:
        pass

    with open(filename, 'rb') as f: 
        sha256sum = hashlib.sha256(f.read()).hexdigest()

    output += f"""
    def test_{testname}(self):
        '''
            Textcase taken from https://github.com/ethereum/tests
            File: {os.path.split(filename)[1]}
            sha256sum: {sha256sum}
            Code: {disassemble}
        '''    
    """
    env = testcase['env']

    gaslimit=int(env['currentGasLimit'], 0)
    blocknumber=int(env['currentNumber'], 0)
    timestamp=int(env['currentTimestamp'], 0)
    difficulty=int(env['currentDifficulty'], 0)
    coinbase=int(env['currentCoinbase'], 0)
    output += f'''
        constraints = ConstraintSet()
        world = evm.EVMWorld(constraints, blocknumber={blocknumber}, timestamp={timestamp}, difficulty={difficulty}, coinbase={coinbase}, gaslimit={gaslimit})
    '''

    for address, account in testcase['pre'].items():
        account_address = int(address, 0)
        account_code = account['code'][2:]
        account_nonce = int(account['nonce'], 0)
        account_balance = int(account['balance'], 0)

        output += f'''
        bytecode = unhexlify('{account_code}')
        world.create_account(address={hex(account_address)},
                             balance={account_balance},
                             code=bytecode,
                             nonce={account_nonce}
                            )'''

        for key, value in account['storage'].items():
            output += f'''
        world.set_storage_data({hex(account_address)}, {key}, {value})'''

    address = int(testcase['exec']['address'], 0)
    caller = int(testcase['exec']['caller'], 0)
    code = testcase['exec']['code'][2:]
    calldata = testcase['exec']['data'][2:]
    gas = int(testcase['exec']['gas'], 0)
    price = int(testcase['exec']['gasPrice'], 0)
    origin = int(testcase['exec']['origin'], 0)
    value = int(testcase['exec']['value'], 0)


    #Need to check if origin is diff from caller. we do not support those tests
    assert origin == caller, "test type not supported"
    assert testcase['pre']['0x{:040x}'.format(address)]['code'][2:] == code, "test type not supported"

    output += f'''
        address = {hex(address)}
        price = {hex(price)}'''

    if calldata:
        output += f'''
        data = unhexlify('{calldata}')'''
    else:
        output += f"""
        data = ''"""

    output += f'''
        caller = {hex(caller)}
        value = {value}
        gas = {gas}

        # open a fake tx, no funds send
        world._open_transaction('CALL', address, price, data, caller, value, gas=gas)

        result = None
        returndata = b''
        try:
            while True:
                world.current_vm.execute()
        except evm.EndTx as e:
            result = e.result
            if e.result in ('RETURN', 'REVERT'):
                returndata = to_constant(e.data)
        except evm.StartTx as e:
            self.fail('This tests should not initiate an internal tx (no CALLs allowed)')'''

    if 'post' not in testcase:
        output +='''
        #If test end in exception ceck it here
        self.assertTrue(result in ('THROW'))'''
    else:

        for address, account in testcase['post'].items():
            account_address = int(address, 0)
            account_code = account['code'][2:]
            account_nonce = int(account['nonce'], 0)
            account_balance = int(account['balance'], 0)

            output += f'''
        #Add pos checks for account {hex(account_address)}
        #check nonce, balance, code
        self.assertEqual(world.get_nonce({hex(account_address)}), {account_nonce})
        self.assertEqual(to_constant(world.get_balance({hex(account_address)})), {account_balance})
        self.assertEqual(world.get_code({hex(account_address)}), unhexlify('{account_code}'))'''

        if account['storage']:
            output += '''
        #check storage'''

            for key, value in account['storage'].items():
                output += f'''
        self.assertEqual(to_constant(world.get_storage_data({hex(account_address)}, {key})), {value})'''

        output += f'''
        #check outs
        self.assertEqual(returndata, unhexlify('{testcase['out'][2:]}'))'''

        output += '''
        #check logs
        data = rlp.encode([Log(unhexlify('{:040x}'.format(l.address)), l.topics, to_constant(l.memlog)) for l in world.logs])'''
        output += f'''
        self.assertEqual(sha3.keccak_256(data).hexdigest(), '{testcase['logs'][2:]}')
        '''

        output += f'''
        # test used gas
        self.assertEqual(to_constant(world.current_vm.gas), {int(testcase['gas'], 0)})'''

    return output
예제 #3
0
def generate_pre_output(testcase, filename, symbolic):
    testname = (os.path.split(filename)[1].replace("-", "_")).split(".")[0]
    bytecode = unhexlify(testcase["exec"]["code"][2:])
    disassemble = ""
    try:
        # add silent=True when evmasm supports it
        disassemble = "\n                  ".join(
            EVMAsm.disassemble(bytecode).split("\n"))
    except Exception as e:
        pass

    with open(filename, "rb") as f:
        sha256sum = hashlib.sha256(f.read()).hexdigest()

    output = f'''
    def test_{testname}(self):
        """
        Testcase taken from https://github.com/ethereum/tests
        File: {os.path.split(filename)[1]}
        sha256sum: {sha256sum}
        Code:     {disassemble}
        """    
    '''

    if symbolic:
        output += """
        def solve(val):
            return self._solve(constraints, val)
"""
    else:
        output += '''
        def solve(val):
            """
            Those tests are **auto-generated** and `solve` is used in symbolic tests.
            So yes, this returns just val; it makes it easier to generate tests like this.
            """
            return to_constant(val)
'''

    env = testcase["env"]

    gaslimit = int(env["currentGasLimit"], 0)
    blocknumber = int(env["currentNumber"], 0)
    timestamp = int(env["currentTimestamp"], 0)
    difficulty = int(env["currentDifficulty"], 0)
    coinbase = int(env["currentCoinbase"], 0)
    output += f"""
        constraints = ConstraintSet()
"""

    def format_bitvec(name, val, bits=256, symbolic_name=None):
        if symbolic_name is None:
            symbolic_name = name

        return f"""
        {name} = constraints.new_bitvec({bits}, name='{symbolic_name}')
        constraints.add({name} == {val})
"""

    def format_var(name, val):
        return f"""
        {name} = {val}"""

    # Spawns/creates bitvectors in symbolic or pure variables in concrete
    formatter = format_bitvec if symbolic else format_var
    for var in ("blocknumber", "timestamp", "difficulty", "coinbase",
                "gaslimit"):
        output += formatter(var, locals()[var])

    output += f"""
        world = evm.EVMWorld(constraints, blocknumber=blocknumber, timestamp=timestamp, difficulty=difficulty,
                             coinbase=coinbase, gaslimit=gaslimit)
    """

    for address, account in testcase["pre"].items():
        assert account.keys() == {"code", "nonce", "balance", "storage"}

        account_address = int(address, 0)
        account_code = account["code"][2:]
        account_nonce = int(account["nonce"], 0)
        account_balance = int(account["balance"], 0)

        if symbolic:
            postfix = hex(account_address)
            output += f"""
        acc_addr = {hex(account_address)}
        acc_code = unhexlify('{account_code}')
            """
            output += format_bitvec("acc_balance",
                                    account_balance,
                                    symbolic_name=f"balance_{postfix}")
            output += format_bitvec("acc_nonce",
                                    account_nonce,
                                    symbolic_name=f"nonce_{postfix}")
        else:
            output += f"""
        acc_addr = {hex(account_address)}
        acc_code = unhexlify('{account_code}')
        acc_balance = {account_balance}
        acc_nonce = {account_nonce}
"""

        output += f"""
        world.create_account(address=acc_addr, balance=acc_balance, code=acc_code, nonce=acc_nonce)
"""
        for key, value in account["storage"].items():
            if symbolic:
                postfix = hex(account_address)
                output += format_bitvec("key",
                                        key,
                                        symbolic_name=f"storage_key_{postfix}")
                output += format_bitvec(
                    "value", value, symbolic_name=f"storage_value_{postfix}")
                output += """
        world.set_storage_data(acc_addr, key, value)
"""
            else:
                output += f"""
        world.set_storage_data(acc_addr, {key}, {value})
"""

    address = int(testcase["exec"]["address"], 0)
    caller = int(testcase["exec"]["caller"], 0)
    code = testcase["exec"]["code"][2:]
    calldata = unhexlify(testcase["exec"]["data"][2:])
    gas = int(testcase["exec"]["gas"], 0)
    price = int(testcase["exec"]["gasPrice"], 0)
    origin = int(testcase["exec"]["origin"], 0)
    value = int(testcase["exec"]["value"], 0)
    assert testcase["exec"].keys() == {
        "address",
        "caller",
        "code",
        "data",
        "gas",
        "gasPrice",
        "origin",
        "value",
    }

    # Need to check if origin is diff from caller. we do not support those tests
    assert origin == caller, "test type not supported"
    assert (testcase["pre"]["0x{:040x}".format(address)]["code"][2:] == code
            ), "test type not supported"

    output += f"""
        address = {hex(address)}
        caller = {hex(caller)}"""

    if symbolic:
        output += format_bitvec("price", price)
        output += format_bitvec("value", value)
        output += format_bitvec("gas", gas)
    else:
        output += f"""
        price = {hex(price)}
        value = {value}
        gas = {gas}"""

    if calldata:
        if symbolic:
            output += f"""
        data = constraints.new_array(index_max={len(calldata)})
        constraints.add(data == {calldata})
"""
        else:
            output += f"""
        data = {calldata}"""
    else:
        output += f"""
        data = ''"""

    output += f"""
        # open a fake tx, no funds send
        world._open_transaction('CALL', address, price, data, caller, value, gas=gas)

        # This variable might seem redundant in some tests - don't forget it is auto generated
        # and there are cases in which we need it ;)
        result, returndata = self._test_run(world)

        # World sanity checks - those should not change, right?
        self.assertEqual(solve(world.block_number()), {blocknumber})
        self.assertEqual(solve(world.block_gaslimit()), {gaslimit})
        self.assertEqual(solve(world.block_timestamp()), {timestamp})
        self.assertEqual(solve(world.block_difficulty()), {difficulty})
        self.assertEqual(solve(world.block_coinbase()), {hex(coinbase)})
"""

    return output
예제 #4
0
def gen_body(name, testcase):
    body = f'''
    def test_{name}(self):
        """
        Testcase taken from https://github.com/ethereum/tests
        Source: {testcase['_info']['source']} 
        """
        class UsedGas(Plugin):
            @property
            def used_gas(self):
                with self.locked_context() as ctx:
                    return ctx['test_used_gas']
            @used_gas.setter
            def used_gas(self, value):
                with self.locked_context() as ctx:
                    ctx['test_used_gas']=value

            def did_close_transaction_callback(self, state, tx):
                if tx.is_human:
                    self.used_gas = tx.used_gas
    
        used_gas_plugin = UsedGas()
        m = ManticoreEVM(workspace_url="mem:", plugins=(used_gas_plugin,))

'''
    for address, account in testcase["pre"].items():
        account_address = int(address, 0)
        account_code = account["code"][2:]
        account_nonce = int(account["nonce"], 0)
        account_balance = int(account["balance"], 0)

        disassembly = EVMAsm.disassemble(unhexlify(account_code),
                                         fork=DEFAULT_FORK.lower())
        disassembly = ('\n        """' + "\n            " +
                       "\n            ".join(disassembly.split("\n")) +
                       '\n        """')
        body += f"""
        {disassembly if account_code else ''}
        m.create_account(address={hex(account_address)},
                         balance={account_balance}, 
                         code={"unhexlify('"+account_code+"')" if account_code else "b''"}, 
                         nonce={account_nonce})"""

        if "storage" in account and account["storage"]:
            body += """
        for state in m.all_states:
            world = state.platform"""

        for key, value in account["storage"].items():
            body += f"""
            world.set_storage_data({hex(account_address)}, {key}, {value})"""

    coinbases = set()
    for block in testcase["blocks"]:
        blockheader = block["blockHeader"]
        coinbases.add(blockheader["coinbase"])
    for coinbase in coinbases:
        body += f"""
        #coinbase
        m.create_account(address={coinbase},
                         balance=0, 
                         code=b'', 
                         nonce=0)
        """

    for block in testcase["blocks"]:
        blockheader = block["blockHeader"]

        body += f"""
        # Start a block
        self.assertEqual(m.count_all_states(), 1)
        m.start_block(blocknumber={blockheader['number']},
                      timestamp={blockheader['timestamp']},
                      difficulty={blockheader['difficulty']},
                      coinbase={blockheader['coinbase']},
                      gaslimit={hex(int(blockheader['gasLimit'],0))})

        #VMtest Transaction
"""

        for transaction in block["transactions"]:
            address = None if transaction["to"] == "" else int(
                transaction["to"], 16)
            calldata = unhexlify(transaction["data"][2:])
            gas = int(transaction["gasLimit"], 0)
            price = int(transaction["gasPrice"], 0)
            nonce = int(transaction["nonce"], 0)
            value = 0 if transaction["value"] == "0x" else int(
                transaction["value"], 0)
            r = int(transaction["r"], 0)
            s = int(transaction["s"], 0)
            v = int(transaction["v"], 0)
            caller = get_caller(nonce, price, gas, address, value, calldata, v,
                                r, s)
            body += f"""

        m.transaction(caller={hex(caller)},
                      address={hex(address)},
                      value={value},
                      data={calldata},
                      gas={gas},
                      price={price})"""

    body += f"""
        for state in m.all_states:
            world = state.platform
            self.assertEqual(used_gas_plugin.used_gas, {blockheader['gasUsed']})
            
            world.end_block()"""

    for account_address, account in testcase["postState"].items():
        body += f"""
            # Add post checks for account {account_address}
            # check nonce, balance, code and storage values
            self.assertEqual(world.get_nonce({account_address}), {account['nonce']})
            self.assertEqual(world.get_balance({account_address}), {account['balance']})
            self.assertEqual(world.get_code({account_address}), {"unhexlify('"+account['code'][2:]+"')" if account['code'][2:] else "b''"})"""

        if account["storage"]:
            body += """
            # check storage"""

            for key, value in account["storage"].items():
                body += f"""
            self.assertEqual(world.get_storage_data({account_address}, {key}), {value})"""

    if "logs" in testcase:
        print(testcase["logs"])
        body += f"""
            # check logs
            logs = [Log(unhexlify('{'{'}:040x{'}'}'.format(l.address)), l.topics, solve(l.memlog)) for l in world.logs]
            data = rlp.encode(logs)
            self.assertEqual(sha3.keccak_256(data).hexdigest(), '{testcase['logs'][2:]}')"""

    return body
예제 #5
0
def generate_pre_output(testcase, filename, symbolic):
    testname = (os.path.split(filename)[1].replace('-', '_')).split('.')[0]
    bytecode = unhexlify(testcase['exec']['code'][2:])
    disassemble = ''
    try:
        # add silent=True when evmasm supports it
        disassemble = '\n                  '.join(
            EVMAsm.disassemble(bytecode).split('\n'))
    except Exception as e:
        pass

    with open(filename, 'rb') as f:
        sha256sum = hashlib.sha256(f.read()).hexdigest()

    output = f'''
    def test_{testname}(self):
        """
        Testcase taken from https://github.com/ethereum/tests
        File: {os.path.split(filename)[1]}
        sha256sum: {sha256sum}
        Code:     {disassemble}
        """    
    '''

    if symbolic:
        output += '''
        def solve(val):
            return self._solve(constraints, val)
'''
    else:
        output += '''
        def solve(val):
            """
            Those tests are **auto-generated** and `solve` is used in symbolic tests.
            So yes, this returns just val; it makes it easier to generate tests like this.
            """
            return to_constant(val)
'''

    env = testcase['env']

    gaslimit = int(env['currentGasLimit'], 0)
    blocknumber = int(env['currentNumber'], 0)
    timestamp = int(env['currentTimestamp'], 0)
    difficulty = int(env['currentDifficulty'], 0)
    coinbase = int(env['currentCoinbase'], 0)
    output += f'''
        constraints = ConstraintSet()
'''

    def format_bitvec(name, val, bits=256, symbolic_name=None):
        if symbolic_name is None:
            symbolic_name = name

        return f'''
        {name} = constraints.new_bitvec({bits}, name='{symbolic_name}')
        constraints.add({name} == {val})
'''

    def format_var(name, val):
        return f'''
        {name} = {val}'''

    # Spawns/creates bitvectors in symbolic or pure variables in concrete
    formatter = format_bitvec if symbolic else format_var
    for var in ('blocknumber', 'timestamp', 'difficulty', 'coinbase',
                'gaslimit'):
        output += formatter(var, locals()[var])

    output += f'''
        world = evm.EVMWorld(constraints, blocknumber=blocknumber, timestamp=timestamp, difficulty=difficulty,
                             coinbase=coinbase, gaslimit=gaslimit)
    '''

    for address, account in testcase['pre'].items():
        assert account.keys() == {'code', 'nonce', 'balance', 'storage'}

        account_address = int(address, 0)
        account_code = account['code'][2:]
        account_nonce = int(account['nonce'], 0)
        account_balance = int(account['balance'], 0)

        if symbolic:
            postfix = hex(account_address)
            output += f'''
        acc_addr = {hex(account_address)}
        acc_code = unhexlify('{account_code}')
            '''
            output += format_bitvec('acc_balance',
                                    account_balance,
                                    symbolic_name=f'balance_{postfix}')
            output += format_bitvec('acc_nonce',
                                    account_nonce,
                                    symbolic_name=f'nonce_{postfix}')
        else:
            output += f'''
        acc_addr = {hex(account_address)}
        acc_code = unhexlify('{account_code}')
        acc_balance = {account_balance}
        acc_nonce = {account_nonce}
'''

        output += f'''
        world.create_account(address=acc_addr, balance=acc_balance, code=acc_code, nonce=acc_nonce)
'''
        for key, value in account['storage'].items():
            if symbolic:
                postfix = hex(account_address)
                output += format_bitvec('key',
                                        key,
                                        symbolic_name=f'storage_key_{postfix}')
                output += format_bitvec(
                    'value', value, symbolic_name=f'storage_value_{postfix}')
                output += '''
        world.set_storage_data(acc_addr, key, value)
'''
            else:
                output += f'''
        world.set_storage_data(acc_addr, {key}, {value})
'''

    address = int(testcase['exec']['address'], 0)
    caller = int(testcase['exec']['caller'], 0)
    code = testcase['exec']['code'][2:]
    calldata = unhexlify(testcase['exec']['data'][2:])
    gas = int(testcase['exec']['gas'], 0)
    price = int(testcase['exec']['gasPrice'], 0)
    origin = int(testcase['exec']['origin'], 0)
    value = int(testcase['exec']['value'], 0)
    assert testcase['exec'].keys() == {
        'address', 'caller', 'code', 'data', 'gas', 'gasPrice', 'origin',
        'value'
    }

    # Need to check if origin is diff from caller. we do not support those tests
    assert origin == caller, "test type not supported"
    assert testcase['pre']['0x{:040x}'.format(
        address)]['code'][2:] == code, "test type not supported"

    output += f'''
        address = {hex(address)}
        caller = {hex(caller)}'''

    if symbolic:
        output += format_bitvec('price', price)
        output += format_bitvec('value', value)
        output += format_bitvec('gas', gas)
    else:
        output += f'''
        price = {hex(price)}
        value = {value}
        gas = {gas}'''

    if calldata:
        if symbolic:
            output += f'''
        data = constraints.new_array(index_max={len(calldata)})
        constraints.add(data == {calldata})
'''
        else:
            output += f'''
        data = {calldata}'''
    else:
        output += f"""
        data = ''"""

    output += f'''
        # open a fake tx, no funds send
        world._open_transaction('CALL', address, price, data, caller, value, gas=gas)

        # This variable might seem redundant in some tests - don't forget it is auto generated
        # and there are cases in which we need it ;)
        result, returndata = self._test_run(world)

        # World sanity checks - those should not change, right?
        self.assertEqual(solve(world.block_number()), {blocknumber})
        self.assertEqual(solve(world.block_gaslimit()), {gaslimit})
        self.assertEqual(solve(world.block_timestamp()), {timestamp})
        self.assertEqual(solve(world.block_difficulty()), {difficulty})
        self.assertEqual(solve(world.block_coinbase()), {hex(coinbase)})
'''

    return output