Merge pull request #7 from status-im/1_print_int128
print int128 support
This commit is contained in:
commit
2c52e05f1b
|
@ -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,
|
||||
)
|
||||
|
@ -100,50 +103,6 @@ if __name__ == '__main__':
|
|||
|
||||
abi = compiler.mk_full_signature(code)
|
||||
|
||||
def serialise_var_rec(var_rec):
|
||||
return {
|
||||
'type': var_rec.typ.typ,
|
||||
'size': var_rec.size * 32,
|
||||
'position': var_rec.pos
|
||||
}
|
||||
|
||||
_contracts, _events, _defs, _globals, _custom_units = parser.get_contracts_and_defs_and_globals(parser.parse(code))
|
||||
source_map = {
|
||||
'globals': {},
|
||||
'locals': {}
|
||||
}
|
||||
source_map['globals'] = {
|
||||
name: serialise_var_rec(var_record)
|
||||
for name, var_record in _globals.items()
|
||||
}
|
||||
# Fetch context for each function.
|
||||
lll = parser.parse_tree_to_lll(parser.parse(code), code, runtime_only=True)
|
||||
contexts = {
|
||||
f.func_name: f.context
|
||||
for f in lll.args[1:] if hasattr(f, 'context')
|
||||
}
|
||||
|
||||
prev_func_name = None
|
||||
for _def in _defs:
|
||||
func_info = {
|
||||
'from_lineno': _def.lineno,
|
||||
'variables': {}
|
||||
}
|
||||
# set local variables for specific function.
|
||||
context = contexts[_def.name]
|
||||
func_info['variables'] = {
|
||||
var_name: serialise_var_rec(var_rec)
|
||||
for var_name, var_rec in context.vars.items()
|
||||
}
|
||||
|
||||
source_map['locals'][_def.name] = func_info
|
||||
# set to_lineno
|
||||
if prev_func_name:
|
||||
source_map['locals'][prev_func_name]['to_lineno'] = _def.lineno
|
||||
prev_func_name = _def.name
|
||||
|
||||
source_map['locals'][_def.name]['to_lineno'] = len(code.splitlines())
|
||||
|
||||
# Format init args.
|
||||
if init_args:
|
||||
init_abi = next(filter(lambda func: func["name"] == '__init__', abi))
|
||||
|
@ -167,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()
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
import pytest
|
||||
|
||||
from vyper import compiler
|
||||
|
||||
from eth_tester import (
|
||||
EthereumTester,
|
||||
)
|
||||
from web3.providers.eth_tester import (
|
||||
EthereumTesterProvider,
|
||||
)
|
||||
from web3 import (
|
||||
Web3,
|
||||
)
|
||||
from vdb.vdb import (
|
||||
set_evm_opcode_debugger,
|
||||
VyperDebugCmd
|
||||
)
|
||||
from vdb.source_map import (
|
||||
produce_source_map
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def tester():
|
||||
t = EthereumTester()
|
||||
return t
|
||||
|
||||
|
||||
def zero_gas_price_strategy(web3, transaction_params=None):
|
||||
return 0 # zero gas price makes testing simpler.
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def w3(tester):
|
||||
w3 = Web3(EthereumTesterProvider(tester))
|
||||
w3.eth.setGasPriceStrategy(zero_gas_price_strategy)
|
||||
return w3
|
||||
|
||||
|
||||
def _get_contract(w3, source_code, *args, **kwargs):
|
||||
abi = compiler.mk_full_signature(source_code)
|
||||
bytecode = '0x' + compiler.compile(source_code).hex()
|
||||
contract = w3.eth.contract(abi=abi, bytecode=bytecode)
|
||||
|
||||
stdin = kwargs['stdin'] if 'stdin' in kwargs else None
|
||||
stdout = kwargs['stdout'] if 'stdout' in kwargs else None
|
||||
|
||||
source_map = produce_source_map(source_code)
|
||||
set_evm_opcode_debugger(
|
||||
source_code=source_code, source_map=source_map, stdin=stdin, stdout=stdout
|
||||
)
|
||||
|
||||
value = kwargs.pop('value', 0)
|
||||
value_in_eth = kwargs.pop('value_in_eth', 0)
|
||||
value = value_in_eth * 10**18 if value_in_eth else value # Handle deploying with an eth value.
|
||||
gasPrice = kwargs.pop('gasPrice', 0)
|
||||
deploy_transaction = {
|
||||
'from': w3.eth.accounts[0],
|
||||
'data': contract._encode_constructor_data(args, kwargs),
|
||||
'value': value,
|
||||
'gasPrice': gasPrice
|
||||
}
|
||||
tx = w3.eth.sendTransaction(deploy_transaction)
|
||||
address = w3.eth.getTransactionReceipt(tx)['contractAddress']
|
||||
contract = w3.eth.contract(address, abi=abi, bytecode=bytecode)
|
||||
# Filter logs.
|
||||
contract._logfilter = w3.eth.filter({
|
||||
'fromBlock': w3.eth.blockNumber - 1,
|
||||
'address': contract.address
|
||||
})
|
||||
return contract
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def get_contract(w3):
|
||||
def get_contract(source_code, *args, **kwargs):
|
||||
return _get_contract(w3, source_code, *args, **kwargs)
|
||||
return get_contract
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def get_last_out():
|
||||
def _get_last_out(stdout):
|
||||
return stdout.getvalue().splitlines()[-2].split(VyperDebugCmd.prompt)[1]
|
||||
return _get_last_out
|
|
@ -0,0 +1,39 @@
|
|||
from vdb.source_map import produce_source_map
|
||||
|
||||
|
||||
def test_source_map_output():
|
||||
code = """
|
||||
a_map: bytes32[bytes32]
|
||||
|
||||
@public
|
||||
def func1(a: int128) -> int128:
|
||||
b: int128 = 2
|
||||
c: int128 = 3
|
||||
g: bytes[10]
|
||||
return a + b + c + 1
|
||||
|
||||
@public
|
||||
def func2(a: int128):
|
||||
x: uint256
|
||||
"""
|
||||
|
||||
sm = produce_source_map(code)
|
||||
|
||||
# globals
|
||||
assert sm['globals']['a_map'] == {
|
||||
'type': 'mapping(bytes32[bytes32])',
|
||||
'size': 0,
|
||||
'position': 0
|
||||
}
|
||||
|
||||
# locals
|
||||
assert sm['locals']['func1'] == {
|
||||
'from_lineno': 4,
|
||||
'to_lineno': 11,
|
||||
'variables': {
|
||||
'a': {'type': 'int128', 'size': 32, 'position': 320},
|
||||
'b': {'type': 'int128', 'size': 32, 'position': 352},
|
||||
'c': {'type': 'int128', 'size': 32, 'position': 384},
|
||||
'g': {'type': 'bytes[10]', 'size': 96, 'position': 416}
|
||||
},
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import io
|
||||
|
||||
|
||||
def test_print_uint256(get_contract, get_last_out):
|
||||
code = """
|
||||
@public
|
||||
def test():
|
||||
a: uint256 = 33343
|
||||
vdb
|
||||
"""
|
||||
|
||||
stdin = io.StringIO(
|
||||
"a\n"
|
||||
)
|
||||
stdout = io.StringIO()
|
||||
|
||||
c = get_contract(code, stdin=stdin, stdout=stdout)
|
||||
c.functions.test().call({"gas": 22000})
|
||||
|
||||
assert get_last_out(stdout) == '33343'
|
||||
|
||||
|
||||
def test_print_int128(get_contract, get_last_out):
|
||||
code = """
|
||||
@public
|
||||
def test():
|
||||
a: int128 = -123
|
||||
vdb
|
||||
"""
|
||||
|
||||
stdin = io.StringIO(
|
||||
"a\n"
|
||||
)
|
||||
stdout = io.StringIO()
|
||||
|
||||
c = get_contract(code, stdin=stdin, stdout=stdout)
|
||||
c.functions.test().call({"gas": 22000})
|
||||
assert get_last_out(stdout) == '-123'
|
1
tox.ini
1
tox.ini
|
@ -38,4 +38,3 @@ basepython=python
|
|||
extras=lint
|
||||
commands=
|
||||
flake8 {toxinidir}/vdb {toxinidir}/tests
|
||||
isort --recursive --check-only --diff {toxinidir}/vdb {toxinidir}/tests
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
from vyper.parser import (
|
||||
parser,
|
||||
)
|
||||
from vyper.types import (
|
||||
get_size_of_type,
|
||||
ByteArrayType,
|
||||
MappingType
|
||||
)
|
||||
|
||||
|
||||
def serialise_var_rec(var_rec):
|
||||
if isinstance(var_rec.typ, ByteArrayType):
|
||||
type_str = 'bytes[%s]' % var_rec.typ.maxlen
|
||||
_size = get_size_of_type(var_rec.typ) * 32
|
||||
elif isinstance(var_rec.typ, MappingType):
|
||||
type_str = 'mapping(%s)' % var_rec.typ
|
||||
_size = 0
|
||||
else:
|
||||
type_str = var_rec.typ.typ
|
||||
_size = get_size_of_type(var_rec.typ) * 32
|
||||
|
||||
out = {
|
||||
'type': type_str,
|
||||
'size': _size,
|
||||
'position': var_rec.pos
|
||||
}
|
||||
return out
|
||||
|
||||
|
||||
def produce_source_map(code):
|
||||
_contracts, _events, _defs, _globals, _custom_units = \
|
||||
parser.get_contracts_and_defs_and_globals(parser.parse(code))
|
||||
source_map = {
|
||||
'globals': {},
|
||||
'locals': {}
|
||||
}
|
||||
source_map['globals'] = {
|
||||
name: serialise_var_rec(var_record)
|
||||
for name, var_record in _globals.items()
|
||||
}
|
||||
# Fetch context for each function.
|
||||
lll = parser.parse_tree_to_lll(parser.parse(code), code, runtime_only=True)
|
||||
contexts = {
|
||||
f.func_name: f.context
|
||||
for f in lll.args[1:] if hasattr(f, 'context')
|
||||
}
|
||||
|
||||
prev_func_name = None
|
||||
for _def in _defs:
|
||||
func_info = {
|
||||
'from_lineno': _def.lineno,
|
||||
'variables': {}
|
||||
}
|
||||
# set local variables for specific function.
|
||||
context = contexts[_def.name]
|
||||
func_info['variables'] = {
|
||||
var_name: serialise_var_rec(var_rec)
|
||||
for var_name, var_rec in context.vars.items()
|
||||
}
|
||||
|
||||
source_map['locals'][_def.name] = func_info
|
||||
# set to_lineno
|
||||
if prev_func_name:
|
||||
source_map['locals'][prev_func_name]['to_lineno'] = _def.lineno
|
||||
prev_func_name = _def.name
|
||||
|
||||
source_map['locals'][_def.name]['to_lineno'] = len(code.splitlines())
|
||||
|
||||
return source_map
|
111
vdb/vdb.py
111
vdb/vdb.py
|
@ -1,23 +1,16 @@
|
|||
import cmd
|
||||
|
||||
# from eth_hash.auto import keccak
|
||||
from eth_utils import (
|
||||
to_hex,
|
||||
)
|
||||
from eth_utils import to_hex
|
||||
from eth_abi import decode_single
|
||||
|
||||
import evm
|
||||
from evm import (
|
||||
constants,
|
||||
)
|
||||
from evm.utils.numeric import ( # ceil32
|
||||
big_endian_to_int,
|
||||
from evm import constants
|
||||
from evm.vm.opcode import as_opcode
|
||||
from evm.utils.numeric import (
|
||||
int_to_big_endian,
|
||||
)
|
||||
from evm.vm.opcode import (
|
||||
as_opcode,
|
||||
)
|
||||
from vyper.opcodes import (
|
||||
opcodes as vyper_opcodes,
|
||||
)
|
||||
from vyper.opcodes import opcodes as vyper_opcodes
|
||||
|
||||
commands = [
|
||||
'continue',
|
||||
|
@ -27,10 +20,10 @@ commands = [
|
|||
base_types = ('int128', 'uint256', 'address', 'bytes32')
|
||||
|
||||
|
||||
def history():
|
||||
def history(stdout):
|
||||
import readline
|
||||
for i in range(1, readline.get_current_history_length() + 1):
|
||||
print("%3d %s" % (i, readline.get_history_item(i)))
|
||||
stdout.write("%3d %s" % (i, readline.get_history_item(i)) + '\n')
|
||||
|
||||
|
||||
logo = """
|
||||
|
@ -43,7 +36,7 @@ __ __
|
|||
"""
|
||||
|
||||
|
||||
def print_var(value, var_typ):
|
||||
def print_var(stdout, value, var_typ):
|
||||
|
||||
if isinstance(value, int):
|
||||
v = int_to_big_endian(value)
|
||||
|
@ -51,33 +44,35 @@ def print_var(value, var_typ):
|
|||
v = value
|
||||
|
||||
if isinstance(v, bytes):
|
||||
if var_typ == 'uint256':
|
||||
print(big_endian_to_int(v))
|
||||
elif var_typ == 'int128':
|
||||
print('TODO!')
|
||||
if var_typ in ('int128', 'uint256'):
|
||||
stdout.write(str(decode_single(var_typ, value)) + '\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()
|
||||
|
@ -86,31 +81,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():
|
||||
|
@ -120,23 +115,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
|
||||
|
@ -145,43 +140,43 @@ class VyperDebugCmd(cmd.Cmd):
|
|||
slot = self.globals[name]['position']
|
||||
elif global_type == 'mapping':
|
||||
# location_hash= keccak(int_to_big_endian(
|
||||
# self.globals[name]['position']).rjust(32, b'\0'))
|
||||
# self.globals[name]['position']).rjust(32, b'\0'))
|
||||
# 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 "{}".\n'.format(global_type))
|
||||
|
||||
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.
|
||||
import pdb; pdb.set_trace() # noqa
|
||||
|
||||
def do_history(self, *args):
|
||||
history()
|
||||
history(self.stdout)
|
||||
|
||||
def emptyline(self):
|
||||
pass
|
||||
|
@ -205,17 +200,17 @@ 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
|
||||
)
|
||||
line_no = computation.stack_pop(num_items=1, type_hint=constants.UINT256)
|
||||
VyperDebugCmd(
|
||||
computation=computation,
|
||||
computation,
|
||||
line_no=line_no,
|
||||
source_code=source_code,
|
||||
source_map=source_map
|
||||
source_map=source_map,
|
||||
stdin=stdin,
|
||||
stdout=stdout
|
||||
).cmdloop()
|
||||
|
||||
opcodes = original_opcodes.copy()
|
||||
|
|
Loading…
Reference in New Issue