Skip to content

Commit d6ea122

Browse files
committed
Refactor CLI
1 parent 9434915 commit d6ea122

File tree

6 files changed

+76
-69
lines changed

6 files changed

+76
-69
lines changed

pyevmasm/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
from .evmasm import EVMAsm
1+
from .evmasm import EVMAsm

evmasm renamed to pyevmasm/__main__.py

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,57 +3,53 @@
33
import sys
44
import binascii
55

6-
from pyevmasm import EVMAsm
6+
from .evmasm import EVMAsm
7+
78

89
def main():
910
parser = argparse.ArgumentParser(description="pyevmasm the EVM assembler and disassembler")
10-
parser.add_argument('-a','--assemble', action='store_true', help='Assemble EVM instructions to opcodes')
11-
parser.add_argument('-d', '--disassemble', action='store_true', help='Disassemble EVM to opcodes')
12-
parser.add_argument('-i', '--input', nargs='?', type=argparse.FileType('r'), default=sys.stdin, help='Input file, default=stdin')
13-
parser.add_argument('-o', '--output', nargs='?', type=argparse.FileType('w'), default=sys.stdout, help='Output file, default=stdout')
14-
parser.add_argument('-t', '--print-opcode-table', action='store_true', help='List supported EVM opcodes')
11+
group_action = parser.add_mutually_exclusive_group(required=True)
12+
group_action.add_argument('-a', '--assemble', action='store_true', help='Assemble EVM instructions to opcodes')
13+
group_action.add_argument('-d', '--disassemble', action='store_true', help='Disassemble EVM to opcodes')
14+
group_action.add_argument('-t', '--print-opcode-table', action='store_true', help='List supported EVM opcodes')
15+
parser.add_argument('-i', '--input', nargs='?', type=argparse.FileType('r'), default=sys.stdin,
16+
help='Input file, default=stdin')
17+
parser.add_argument('-o', '--output', nargs='?', type=argparse.FileType('w'), default=sys.stdout,
18+
help='Output file, default=stdout')
1519

1620
args = parser.parse_args(sys.argv[1:])
1721

1822
if args.print_opcode_table:
1923
table = EVMAsm._get_reverse_table()
2024
for mnemonic in table.keys():
2125
# This relies on the internal format
22-
(opcode, name, immediate_operand_size, pops, pushes, gas, description) = table[mnemonic]
26+
(opcode, name, immediate_operand_size, pops, pushes, gas, description) = table[mnemonic]
2327
print("%02x: %-16s %s" % (opcode, mnemonic, description))
24-
2528
sys.exit(0)
2629

27-
if args.assemble and args.disassemble:
28-
print("You cannot both assemble and disassemble at the same time.")
29-
sys.exit(1)
30-
31-
if not args.assemble and not args.disassemble:
32-
args.disassemble = True
33-
3430
if args.assemble:
3531
asm = args.input.read().strip().rstrip()
3632
args.output.write(EVMAsm.assemble_hex(asm) + "\n")
3733

3834
if args.disassemble:
3935
buf = args.input.read().strip().rstrip()
40-
if buf[:3] == 'EVM': # binja prefix
36+
if buf[:3] == 'EVM': # binja prefix
4137
buf = buf[3:]
42-
elif buf[:2] == '0x': # hex prefixed
38+
elif buf[:2] == '0x': # hex prefixed
4339
buf = binascii.unhexlify(buf[2:])
44-
else: # detect all hex buffer
40+
else: # detect all hex buffer
4541
buf_set = set()
4642
for c in buf:
4743
buf_set.add(c.lower())
4844

4945
hex_set = set(list('0123456789abcdef'))
50-
if buf_set <= hex_set: # subset
46+
if buf_set <= hex_set: # subset
5147
buf = binascii.unhexlify(buf)
52-
48+
5349
insns = list(EVMAsm.disassemble_all(buf))
5450
for i in insns:
55-
args.output.write("%08x: %s\n" %(i.pc, str(i)))
51+
args.output.write("%08x: %s\n" % (i.pc, str(i)))
52+
5653

5754
if __name__ == "__main__":
5855
main()
59-

pyevmasm/evmasm.py

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class EVMAsm(object):
3232
>>> EVMAsm.disassemble_hex('0x606040526002610100')
3333
'PUSH1 0x60\\nBLOCKHASH\\nMSTORE\\nPUSH1 0x2\\nPUSH2 0x100'
3434
'''
35+
3536
class Instruction(object):
3637
def __init__(self, opcode, name, operand_size, pops, pushes, fee, description, operand=None, pc=0):
3738
'''
@@ -89,19 +90,20 @@ def __init__(self, opcode, name, operand_size, pops, pushes, fee, description, o
8990

9091
def __eq__(self, other):
9192
''' Instructions are equal if all features match '''
92-
return self._opcode == other._opcode and\
93-
self._name == other._name and\
94-
self._operand == other._operand and\
95-
self._operand_size == other._operand_size and\
96-
self._pops == other._pops and\
97-
self._pushes == other._pushes and\
98-
self._fee == other._fee and\
99-
self._pc == other._pc and\
100-
self._description == other._description
93+
return self._opcode == other._opcode and \
94+
self._name == other._name and \
95+
self._operand == other._operand and \
96+
self._operand_size == other._operand_size and \
97+
self._pops == other._pops and \
98+
self._pushes == other._pushes and \
99+
self._fee == other._fee and \
100+
self._pc == other._pc and \
101+
self._description == other._description
101102

102103
def __repr__(self):
103-
output = 'Instruction(0x%x, %r, %d, %d, %d, %d, %r, %r, %r)' % (self._opcode, self._name, self._operand_size,
104-
self._pops, self._pushes, self._fee, self._description, self._operand, self._pc)
104+
output = 'Instruction(0x%x, %r, %d, %d, %d, %d, %r, %r, %r)' % (
105+
self._opcode, self._name, self._operand_size,
106+
self._pops, self._pushes, self._fee, self._description, self._operand, self._pc)
105107
return output
106108

107109
def __str__(self):
@@ -294,7 +296,8 @@ def uses_block_info(self):
294296
@property
295297
def is_arithmetic(self):
296298
''' True if the instruction is an arithmetic operation '''
297-
return self.semantics in ('ADD', 'MUL', 'SUB', 'DIV', 'SDIV', 'MOD', 'SMOD', 'ADDMOD', 'MULMOD', 'EXP', 'SIGNEXTEND')
299+
return self.semantics in (
300+
'ADD', 'MUL', 'SUB', 'DIV', 'SDIV', 'MOD', 'SMOD', 'ADDMOD', 'MULMOD', 'EXP', 'SIGNEXTEND')
298301

299302
# from http://gavwood.com/paper.pdf
300303
_table = { # opcode: (name, immediate_operand_size, pops, pushes, gas, description)
@@ -327,7 +330,8 @@ def is_arithmetic(self):
327330
0x31: ('BALANCE', 0, 1, 1, 20, 'Get balance of the given account.'),
328331
0x32: ('ORIGIN', 0, 0, 1, 2, 'Get execution origination address.'),
329332
0x33: ('CALLER', 0, 0, 1, 2, 'Get caller address.'),
330-
0x34: ('CALLVALUE', 0, 0, 1, 2, 'Get deposited value by the instruction/transaction responsible for this execution.'),
333+
0x34: (
334+
'CALLVALUE', 0, 0, 1, 2, 'Get deposited value by the instruction/transaction responsible for this execution.'),
331335
0x35: ('CALLDATALOAD', 0, 1, 1, 3, 'Get input data of current environment.'),
332336
0x36: ('CALLDATASIZE', 0, 0, 1, 2, 'Get size of input data in current environment.'),
333337
0x37: ('CALLDATACOPY', 0, 3, 0, 3, 'Copy input data in current environment to memory.'),
@@ -336,7 +340,8 @@ def is_arithmetic(self):
336340
0x3a: ('GASPRICE', 0, 0, 1, 2, 'Get price of gas in current environment.'),
337341
0x3b: ('EXTCODESIZE', 0, 1, 1, 20, "Get size of an account's code."),
338342
0x3c: ('EXTCODECOPY', 0, 4, 0, 20, "Copy an account's code to memory."),
339-
0x3d: ('RETURNDATASIZE', 0, 0, 1, 2, 'Get size of output data from the previous call from the current environment'),
343+
0x3d: (
344+
'RETURNDATASIZE', 0, 0, 1, 2, 'Get size of output data from the previous call from the current environment'),
340345
0x3e: ('RETURNDATACOPY', 0, 3, 0, 3, 'Copy output data from the previous call to memory'),
341346
0x40: ('BLOCKHASH', 0, 1, 1, 20, 'Get the hash of one of the 256 most recent complete blocks.'),
342347
0x41: ('COINBASE', 0, 0, 1, 2, "Get the block's beneficiary address."),
@@ -354,7 +359,8 @@ def is_arithmetic(self):
354359
0x57: ('JUMPI', 0, 2, 0, 10, 'Conditionally alter the program counter.'),
355360
0x58: ('GETPC', 0, 0, 1, 2, 'Get the value of the program counter prior to the increment.'),
356361
0x59: ('MSIZE', 0, 0, 1, 2, 'Get the size of active memory in bytes.'),
357-
0x5a: ('GAS', 0, 0, 1, 2, 'Get the amount of available gas, including the corresponding reduction the amount of available gas.'),
362+
0x5a: ('GAS', 0, 0, 1, 2,
363+
'Get the amount of available gas, including the corresponding reduction the amount of available gas.'),
358364
0x5b: ('JUMPDEST', 0, 0, 0, 1, 'Mark a valid destination for jumps.'),
359365
0x60: ('PUSH', 1, 0, 1, 0, 'Place 1 byte item on stack.'),
360366
0x61: ('PUSH', 2, 0, 1, 0, 'Place 2-byte item on stack.'),
@@ -429,9 +435,11 @@ def is_arithmetic(self):
429435
0xf1: ('CALL', 0, 7, 1, 40, 'Message-call into an account.'),
430436
0xf2: ('CALLCODE', 0, 7, 1, 40, "Message-call into this account with alternative account's code."),
431437
0xf3: ('RETURN', 0, 2, 0, 0, 'Halt execution returning output data.'),
432-
0xf4: ('DELEGATECALL', 0, 6, 1, 40, "Message-call into this account with an alternative account's code, but persisting into this account with an alternative account's code."),
438+
0xf4: ('DELEGATECALL', 0, 6, 1, 40,
439+
"Message-call into this account with an alternative account's code, but persisting into this account with an alternative account's code."),
433440
0xfa: ('STATICCALL', 0, 6, 1, 40, 'Static message-call into an account.'),
434-
0xfd: ('REVERT', 0, 2, 0, 0, 'Stop execution and revert state changes, without consuming all provided gas and providing a reason.'),
441+
0xfd: ('REVERT', 0, 2, 0, 0,
442+
'Stop execution and revert state changes, without consuming all provided gas and providing a reason.'),
435443
0xfe: ('INVALID', 0, 0, 0, 0, 'Designated invalid instruction.'),
436444
0xff: ('SELFDESTRUCT', 0, 1, 0, 5000, 'Halt execution and register account for later deletion.')
437445
}
@@ -476,7 +484,8 @@ def assemble_one(assembler, pc=0):
476484
assert len(assembler) == 1
477485
operand = None
478486

479-
return EVMAsm.Instruction(opcode, name, operand_size, pops, pushes, gas, description, operand=operand, pc=pc)
487+
return EVMAsm.Instruction(opcode, name, operand_size, pops, pushes, gas, description, operand=operand,
488+
pc=pc)
480489
except BaseException:
481490
raise Exception("Something wrong at pc %d" % pc)
482491

@@ -670,4 +679,4 @@ def assemble_hex(asmcode, pc=0):
670679
...
671680
"0x6060604052600261010"
672681
'''
673-
return '0x' + hexlify(EVMAsm.assemble(asmcode, pc=pc).encode()).decode()
682+
return '0x' + hexlify(EVMAsm.assemble(asmcode, pc=pc).encode()).decode()

pyevmasm/util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,4 @@ def __repr__(self):
3333

3434
def __get__(self, obj, objtype):
3535
'''Support instance methods.'''
36-
return functools.partial(self.__call__, obj)
36+
return functools.partial(self.__call__, obj)

setup.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
name='pyevmasm',
55
version='0.1.0',
66
description='Ethereum Virtual Machine (EVM) assembler and disassembler',
7-
scripts=['evmasm'],
87
author='Trail of Bits',
98
author_email='evmasm@trailofbits.com',
109
url='https://github.com/trailofbits/pyevmasm',
@@ -13,10 +12,15 @@
1312
python_requires='>2.7',
1413
install_requires=[
1514
'future'
16-
],
15+
],
1716
extras_require={
1817
'dev': [
1918
'nose'
20-
]
21-
}
19+
]
20+
},
21+
entry_points={
22+
'console_scripts': [
23+
'evmasm = pyevmasm.__main__:main'
24+
]
25+
}
2226
)

tests/test_EVMAssembler.py

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,48 +3,46 @@
33
from pyevmasm import EVMAsm
44

55

6+
# noinspection PyPep8Naming
67
class EVMTest_Assembler(unittest.TestCase):
78
_multiprocess_can_split_ = True
8-
maxDiff=None
9+
maxDiff = None
910

1011
def test_ADD_1(self):
1112
instruction = EVMAsm.disassemble_one('\x60\x10')
1213
self.assertEqual(EVMAsm.Instruction(0x60, 'PUSH', 1, 0, 1, 0, 'Place 1 byte item on stack.', 16, 0),
1314
instruction)
1415

15-
1616
instruction = EVMAsm.assemble_one('PUSH1 0x10')
1717
EVMAsm.Instruction(0x60, 'PUSH', 1, 0, 1, 0, 'Place 1 byte item on stack.', 16, 0)
18-
18+
1919
instructions1 = EVMAsm.disassemble_all('\x30\x31')
2020
instructions2 = EVMAsm.assemble_all('ADDRESS\nBALANCE')
21-
self.assertTrue( all(a == b for a,b in zip(instructions1, instructions2)))
21+
self.assertTrue(all(a == b for a, b in zip(instructions1, instructions2)))
2222

23-
#High level simple assembler/disassembler
23+
# High level simple assembler/disassembler
2424

2525
bytecode = EVMAsm.assemble_hex(
26-
"""PUSH1 0x60
27-
BLOCKHASH
28-
MSTORE
29-
PUSH1 0x2
30-
PUSH2 0x100
31-
"""
32-
)
26+
"""PUSH1 0x60
27+
BLOCKHASH
28+
MSTORE
29+
PUSH1 0x2
30+
PUSH2 0x100
31+
"""
32+
)
3333
self.assertEqual(bytecode, '0x606040526002610100')
3434

35-
asmcode = EVMAsm.disassemble_hex('0x606040526002610100')
35+
asmcode = EVMAsm.disassemble_hex('0x606040526002610100')
3636
self.assertEqual(asmcode, '''PUSH1 0x60\nBLOCKHASH\nMSTORE\nPUSH1 0x2\nPUSH2 0x100''')
3737

38-
def test_STOP(self):
39-
insn = EVMAsm.disassemble_one('\x00')
40-
self.assertTrue(str(insn) == 'STOP')
41-
42-
def test_JUMPI(self):
43-
insn = EVMAsm.disassemble_one('\x57')
44-
self.assertTrue(str(insn) == 'JUMPI')
45-
self.assertTrue(insn.is_branch)
46-
38+
def test_STOP(self):
39+
insn = EVMAsm.disassemble_one('\x00')
40+
self.assertTrue(str(insn) == 'STOP')
4741

42+
def test_JUMPI(self):
43+
insn = EVMAsm.disassemble_one('\x57')
44+
self.assertTrue(str(insn) == 'JUMPI')
45+
self.assertTrue(insn.is_branch)
4846

4947

5048
if __name__ == '__main__':

0 commit comments

Comments
 (0)