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)
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
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
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
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