Add stdin and stdout passthrough to VyperDebugCmd.

- Used for implementing cmdline testing.
This commit is contained in:
Jacques Wagener 2018-07-18 15:42:18 +02:00
parent 5b243dde30
commit 093541cac6
2 changed files with 49 additions and 35 deletions

View File

@ -11,6 +11,9 @@ from vdb.vdb import (
set_evm_opcode_debugger,
set_evm_opcode_pass
)
from vdb.source_map import (
produce_source_map
)
from eth_tester import (
EthereumTester,
)
@ -123,6 +126,7 @@ if __name__ == '__main__':
cast_args = cast_types(args, func_abi)
res = getattr(contract.functions, func_name)(*cast_args).call({'gas': func_abi['gas'] + 22000})
source_map = produce_source_map(code)
set_evm_opcode_debugger(source_code=code, source_map=source_map)
tx_hash = getattr(contract.functions, func_name)(*cast_args).transact({'gas': func_abi['gas'] + 22000})
set_evm_opcode_pass()

View File

@ -24,7 +24,7 @@ base_types = ('int128', 'uint256', 'address', 'bytes32')
def history():
import readline
for i in range(1, readline.get_current_history_length() + 1):
print("%3d %s" % (i, readline.get_history_item(i)))
self.stdout.write("%3d %s" % (i, readline.get_history_item(i)) + '\n')
logo = """
@ -37,7 +37,7 @@ __ __
"""
def print_var(value, var_typ):
def print_var(stdout, value, var_typ):
if isinstance(value, int):
v = int_to_big_endian(value)
@ -46,32 +46,35 @@ def print_var(value, var_typ):
if isinstance(v, bytes):
if var_typ == 'uint256':
print(big_endian_to_int(v))
stdout.write(str(big_endian_to_int(v)) + '\n')
elif var_typ == 'int128':
print('TODO!')
stdout.write('TODO!' + '\n')
elif var_typ == 'address':
print(to_hex(v[12:]))
stdout.write(to_hex(v[12:]) + '\n')
else:
print(v)
stdout.write(v + '\n')
class VyperDebugCmd(cmd.Cmd):
def __init__(self, computation, line_no=None, source_code=None, source_map=None):
prompt = '\033[92mvdb\033[0m> '
intro = logo
def __init__(self, computation, line_no=None, source_code=None, source_map=None, stdout=None, stdin=None):
if source_map is None:
source_map = {}
self.computation = computation
self.prompt = '\033[92mvdb\033[0m> '
self.intro = logo
self.source_code = source_code
self.line_no = line_no
self.globals = source_map.get("globals")
self.locals = source_map.get("locals")
super().__init__()
super().__init__(stdin=stdin, stdout=stdout)
if stdout or stdin:
self.use_rawinput = False
def _print_code_position(self):
if not all((self.source_code, self.line_no)):
print('No source loaded')
self.stdout.write('No source loaded' + '\n')
return
lines = self.source_code.splitlines()
@ -80,31 +83,31 @@ class VyperDebugCmd(cmd.Cmd):
for idx, line in enumerate(lines[begin - 1:end]):
line_number = begin + idx
if line_number == self.line_no:
print("--> \033[92m{}\033[0m\t{}".format(line_number, line))
self.stdout.write("--> \033[92m{}\033[0m\t{}".format(line_number, line) + '\n')
else:
print(" \033[92m{}\033[0m\t{}".format(line_number, line))
self.stdout.write(" \033[92m{}\033[0m\t{}".format(line_number, line) + '\n')
def preloop(self):
super().preloop()
self._print_code_position()
def postloop(self):
print('Exiting vdb')
self.stdout.write('Exiting vdb' + '\n')
super().postloop()
def do_state(self, *args):
""" Show current EVM state information. """
print('Block Number => {}'.format(self.computation.state.block_number))
print('Program Counter => {}'.format(self.computation.code.pc))
print('Memory Size => {}'.format(len(self.computation._memory)))
print('Gas Remaining => {}'.format(self.computation.get_gas_remaining()))
self.stdout.write('Block Number => {}'.format(self.computation.state.block_number) + '\n')
self.stdout.write('Program Counter => {}'.format(self.computation.code.pc) + '\n')
self.stdout.write('Memory Size => {}'.format(len(self.computation._memory)) + '\n')
self.stdout.write('Gas Remaining => {}'.format(self.computation.get_gas_remaining()) + '\n')
def do_globals(self, *args):
if not self.globals:
print('No globals found.')
print('Name\t\tType')
self.stdout.write('No globals found.' + '\n')
self.stdout.write('Name\t\tType' + '\n')
for name, info in self.globals.items():
print('self.{}\t\t{}'.format(name, info['type']))
self.stdout.write('self.{}\t\t{}'.format(name, info['type']) + '\n')
def _get_fn_name_locals(self):
for fn_name, info in self.locals.items():
@ -114,23 +117,23 @@ class VyperDebugCmd(cmd.Cmd):
def do_locals(self, *args):
if not self.locals:
print('No locals found.')
self.stdout.write('No locals found.' + '\n')
fn_name, variables = self._get_fn_name_locals()
print('Function: {}'.format(fn_name))
print('Name\t\tType')
self.stdout.write('Function: {}'.format(fn_name) + '\n')
self.stdout.write('Name\t\tType' + '\n')
for name, info in variables.items():
print('{}\t\t{}'.format(name, info['type']))
self.stdout.write('{}\t\t{}'.format(name, info['type']) + '\n')
def default(self, line):
fn_name, local_variables = self._get_fn_name_locals()
if line.startswith('self.') and len(line) > 4:
if not self.globals:
print('No globals found.')
self.stdout.write('No globals found.' + '\n')
# print global value.
name = line.split('.')[1]
if name not in self.globals:
print('Global named "{}" not found.'.format(name))
self.stdout.write('Global named "{}" not found.'.format(name) + '\n')
else:
global_type = self.globals[name]['type']
slot = None
@ -142,32 +145,32 @@ class VyperDebugCmd(cmd.Cmd):
# slot = big_endian_to_int(location_hash)
pass
else:
print('Can not read global of type "{}".'.format(global_type))
self.stdout.write('Can not read global of type "{}".'.format(global_type) + '\n')
if slot is not None:
value = self.computation.state.account_db.get_storage(
address=self.computation.msg.storage_address,
slot=slot,
)
print_var(value, global_type)
print_var(self.stdout, value, global_type)
elif line in local_variables:
var_info = local_variables[line]
local_type = var_info['type']
if local_type in base_types:
start_position = var_info['position']
value = self.computation.memory_read(start_position, 32)
print_var(value, local_type)
print_var(self.stdout, value, local_type)
else:
print('Can not read local of type ')
self.stdout.write('Can not read local of type ' + '\n')
else:
self.stdout.write('*** Unknown syntax: %s\n' % line)
def do_stack(self, *args):
""" Show contents of the stack """
for idx, value in enumerate(self.computation._stack.values):
print("{}\t{}".format(idx, to_hex(value)))
self.stdout.write("{}\t{}".format(idx, to_hex(value)) + '\n')
else:
print("Stack is empty")
self.stdout.write("Stack is empty" + '\n')
def do_pdb(self, *args):
# Break out to pdb for vdb debugging.
@ -198,11 +201,18 @@ class VyperDebugCmd(cmd.Cmd):
original_opcodes = evm.vm.forks.byzantium.computation.ByzantiumComputation.opcodes
def set_evm_opcode_debugger(source_code=None, source_map=None):
def set_evm_opcode_debugger(source_code=None, source_map=None, stdin=None, stdout=None):
def debug_opcode(computation):
line_no = computation.stack_pop(num_items=1, type_hint=constants.UINT256)
VyperDebugCmd(computation, line_no=line_no, source_code=source_code, source_map=source_map).cmdloop()
VyperDebugCmd(
computation,
line_no=line_no,
source_code=source_code,
source_map=source_map,
stdin=stdin,
stdout=stdout
).cmdloop()
opcodes = original_opcodes.copy()
opcodes[vyper_opcodes['DEBUG'][0]] = as_opcode(