├── .gitignore ├── .travis.yml ├── README.md ├── evmdasm ├── __init__.py ├── __main__.py ├── argtypes.py ├── disassembler.py ├── instruction_registry.py ├── instructions.py ├── registry.py └── utils.py ├── setup.py └── tests ├── test_disassembler.py ├── test_evmprogram.py ├── test_instruction.py └── test_registry.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6" 4 | install: 5 | - pip install nosexcover 6 | - python setup.py install 7 | script: 8 | - python setup.py nosetests --with-xcoverage --cover-package=evmdasm --cover-html --cover-branch --xcoverage-file=cobertura.xml 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://api.travis-ci.org/tintinweb/evmdasm.svg?branch=master)](https://travis-ci.org/tintinweb/evmdasm/) 2 | 3 | # evmdasm 4 | *A lightweight ethereum evm bytecode instruction registry, disassembler and evmcode manipulation library.* 5 | 6 | This library is meant for providing a static interface and registry for EVM opcodes and instructions. The idea is to keep it as lightweight as possible especially when it comes to dependencies or high level features. 7 | 8 | e.g. The [ethereum-dasm](https://github.com/tintinweb/ethereum-dasm) project - a kind of high level disassembler with static/dynamic analysis features - relies on the registry and base disassembling functionality provided by [evmdasm](https://github.com/tintinweb/evmdasm). 9 | 10 | 11 | **More information** --> **[Wiki](https://github.com/tintinweb/evmdasm/wiki)** 12 | 13 | Projects building on [evmdasm](https://github.com/tintinweb/evmdasm/): 14 | * :trophy: https://github.com/ethereum/evmlab 15 | * :trophy: https://github.com/tintinweb/ethereum-dasm 16 | * :trophy: https://github.com/tintinweb/evmcodegen 17 | 18 | ### Setup 19 | 20 | ##### from pypi 21 | ``` 22 | #> python3 -m pip install evmdasm 23 | ``` 24 | 25 | ##### from source 26 | ``` 27 | #> python3 setup.py install 28 | ``` 29 | -------------------------------------------------------------------------------- /evmdasm/__init__.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Author : 4 | from .instructions import Instruction 5 | from .disassembler import EvmBytecode, EvmInstructions, EvmDisassembler, EvmProgram 6 | 7 | 8 | __ALL__ = ["Instruction", "EvbBytecode", "EvmInstructions", "EvmDisassembler", "EvmProgram"] 9 | -------------------------------------------------------------------------------- /evmdasm/__main__.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Author : 4 | 5 | from .disassembler import EvmBytecode 6 | from . import registry 7 | 8 | import sys, os 9 | import logging 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | def main(): 14 | nerrors = 0 15 | 16 | logging.basicConfig(format="%(levelname)-7s - %(message)s", level=logging.WARNING) 17 | from optparse import OptionParser 18 | 19 | parser = OptionParser() 20 | loglevels = ['CRITICAL', 'FATAL', 'ERROR', 'WARNING', 'WARN', 'INFO', 'DEBUG', 'NOTSET'] 21 | parser.add_option("-v", "--verbosity", default="critical", 22 | help="available loglevels: %s [default: %%default]" % ','.join(l.lower() for l in loglevels)) 23 | 24 | parser.add_option("-l", "--list", action="store_true", help="list instructions") 25 | parser.add_option("-d", "--disassemble", action="store_true", help="disassemble input") 26 | # parse args 27 | (options, args) = parser.parse_args() 28 | 29 | if options.verbosity.upper() in loglevels: 30 | options.verbosity = getattr(logging, options.verbosity.upper()) 31 | logger.setLevel(options.verbosity) 32 | else: 33 | parser.error("invalid verbosity selected. please check --help") 34 | 35 | #/////////////////// 36 | #/////////!\//////// 37 | #/////////////////// 38 | 39 | if options.list: 40 | print(" %-2s | %-20s %-20s %-20s" % ('op', 'instruction', 'category', 'gas')) 41 | print("=" * 60) 42 | for instr in sorted(registry.INSTRUCTIONS_BY_OPCODE.values(), key=lambda o:o.opcode): 43 | line = "0x%-2x | %-20s %-20s %-20s"% (instr.opcode, str(instr).strip('\x00'), instr.category, instr.gas) 44 | if args and any(a.lower() in line.lower() for a in args): 45 | print(line) 46 | elif not args: 47 | print(line) 48 | 49 | elif options.disassemble: 50 | if not sys.stdin.isatty(): 51 | args.append(sys.stdin.read().strip()) 52 | for a in args: 53 | if os.path.isfile(a): 54 | with open(a, 'r') as f: 55 | a = f.read() 56 | 57 | bytecode = EvmBytecode(a) 58 | disassembly = bytecode.disassemble() 59 | nerrors = len(disassembly.errors) 60 | print(disassembly.as_string) 61 | if nerrors: 62 | logger.warning("Disassembler finished with %d errors" % nerrors) 63 | 64 | else: 65 | parser.error("not implemented. check --help") 66 | nerrors = -1 67 | 68 | # -- exit -- 69 | sys.exit(nerrors) 70 | 71 | 72 | if __name__ == "__main__": 73 | main() 74 | -------------------------------------------------------------------------------- /evmdasm/argtypes.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Author : 4 | 5 | 6 | class BaseArgument(str): pass 7 | 8 | # todo: override ID to make it compare to the same 9 | 10 | 11 | class Internal(BaseArgument): 12 | _type = "internal" 13 | 14 | 15 | class Value(BaseArgument): 16 | _type = "int64" 17 | 18 | 19 | class Address(BaseArgument): 20 | _type = "int160" 21 | 22 | 23 | class Label(BaseArgument): 24 | _type = "label" 25 | 26 | 27 | class Bool(BaseArgument): 28 | _type = "boolean" 29 | 30 | 31 | class Byte(BaseArgument): 32 | _type = "byte" 33 | 34 | 35 | class Word(BaseArgument): 36 | _type = "word" 37 | 38 | 39 | class Index32(BaseArgument): 40 | _type = "index32" 41 | 42 | 43 | class Index64(BaseArgument): 44 | _type = "index64" 45 | 46 | 47 | class Index256(BaseArgument): 48 | _type = "index256" 49 | 50 | 51 | class MemOffset(BaseArgument): 52 | _type = "memoffset" 53 | 54 | 55 | class Length(BaseArgument): 56 | _type = "length" 57 | 58 | 59 | class Gas(BaseArgument): 60 | _type = "gas" 61 | 62 | 63 | class CallValue(BaseArgument): 64 | _type = "callvalue" 65 | 66 | 67 | class Data(BaseArgument): 68 | _type = "data" 69 | 70 | 71 | class Timestamp(BaseArgument): 72 | _type = "timestamp" 73 | -------------------------------------------------------------------------------- /evmdasm/disassembler.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Author : 4 | import logging 5 | from . import registry, utils 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | class EvmDisassembler(object): 11 | 12 | def __init__(self, debug=False, _registry=None): 13 | self.errors = [] 14 | self.debug = debug 15 | self._registry = _registry if _registry is not None else registry.registry 16 | 17 | def disassemble(self, bytecode): 18 | """ Disassemble evm bytecode to a Instruction objects """ 19 | 20 | pc = 0 21 | previous = None 22 | 23 | if not isinstance(bytecode, EvmBytecode): 24 | # normalize input 25 | evmbytecode = EvmBytecode(bytecode) 26 | 27 | iter_bytecode = iter(evmbytecode.as_bytes) 28 | 29 | # disassemble 30 | seen_stop = False 31 | for opcode in iter_bytecode: 32 | logger.debug(opcode) 33 | try: 34 | instruction = self._registry.by_opcode[opcode].consume(iter_bytecode) 35 | if not len(instruction.operand_bytes)==instruction.length_of_operand: 36 | logger.error("invalid instruction: %s" % instruction.name) 37 | instruction._name = "INVALID_%s" % hex(opcode) 38 | instruction._description = "Invalid operand" 39 | instruction._category = "unknown" 40 | 41 | except KeyError as ke: 42 | instruction = self._registry._template_cls(opcode=opcode, 43 | name="UNKNOWN_%s" % hex(opcode), 44 | description="Invalid opcode", 45 | category="unknown") 46 | 47 | if not seen_stop: 48 | msg = "error: byte at address %d (%s) is not a valid operator" % (pc, hex(opcode)) 49 | if self.debug: 50 | logger.exception(msg) 51 | self.errors.append("%s; %r" % (msg, ke)) 52 | if instruction.name == 'STOP' and not seen_stop: 53 | seen_stop = True 54 | 55 | instruction.address = pc 56 | pc += instruction.size 57 | # doubly link 58 | instruction.previous = previous 59 | if previous: 60 | previous.next = instruction 61 | 62 | # current is previous 63 | previous = instruction 64 | 65 | logger.debug("%s: %s %s" % (hex(instruction.address), instruction.name, instruction.operand)) 66 | yield instruction 67 | 68 | def assemble(self, instructions): 69 | """ Assemble a list of Instruction() objects to evm bytecode""" 70 | for instruction in instructions: 71 | yield instruction.serialize() 72 | 73 | 74 | class EvmBytecode(object): 75 | 76 | def __init__(self, bytecode): 77 | self.bytecode = EvmBytecode.normalize_bytecode(bytecode) 78 | self.errors = [] 79 | 80 | def __str__(self): 81 | return "0x%s" % self.as_hexstring 82 | 83 | @staticmethod 84 | def normalize_bytecode(bytecode): 85 | # check if input is hexstr or bytestr and remove any prefixes. we're working on bytecode 86 | if isinstance(bytecode, bytes): 87 | return utils.bytes_to_str(bytecode, prefix='') 88 | elif isinstance(bytecode, str): 89 | bytecode = utils.strip_0x_prefix(bytecode.strip()) 90 | if utils.is_hexstring(bytecode): 91 | return bytecode 92 | 93 | raise Exception("invalid input format. hexstring (0x) or bytes accepted. %r"%bytecode) 94 | 95 | def disassemble(self): 96 | disassembler = EvmDisassembler() 97 | self.instructions = EvmInstructions(list(disassembler.disassemble(self.as_bytes))) 98 | self.errors = disassembler.errors 99 | return self.instructions 100 | 101 | @property 102 | def as_hexstring(self): 103 | return self.bytecode 104 | 105 | @property 106 | def as_bytes(self): 107 | return utils.str_to_bytes(self.bytecode) 108 | 109 | 110 | class EvmInstructions(list): 111 | 112 | def __init__(self, instructions=[], fix_addresses=True): 113 | super().__init__(instructions) 114 | 115 | self._fix_addresses = fix_addresses 116 | self._fix_addresses_at_index = 0 # start fixing from item 0 117 | self._fix_addresses_required = False 118 | 119 | self.errors = [] 120 | 121 | def __iter__(self): 122 | self._update_instruction_addresses(at_index=self._fix_addresses_at_index) 123 | return super().__iter__() 124 | 125 | def __getitem__(self, index): 126 | if isinstance(index, int): 127 | stop = index 128 | elif isinstance(index, slice): 129 | stop = index.stop 130 | else: 131 | raise TypeError("index must be int or slice") 132 | 133 | if stop >= self._fix_addresses_at_index: 134 | # no need to fix addresses if we're accessing an item that has a valid address 135 | self._update_instruction_addresses(at_index=self._fix_addresses_at_index) 136 | return super().__getitem__(index) 137 | 138 | def pop(self, *args, **kwargs): 139 | self._update_instruction_addresses(at_index=self._fix_addresses_at_index) 140 | return super().pop(*args, **kwargs) 141 | 142 | def index(self, *args, **kwargs): 143 | self._update_instruction_addresses(at_index=self._fix_addresses_at_index) 144 | return super().index(*args, **kwargs) 145 | 146 | def __delitem__(self, i): 147 | self._fix_addresses_required = True 148 | self._fix_addresses_at_index = min(self._fix_addresses_at_index, 149 | i if i >= 0 else ((len(self) - i) % len(self))) 150 | return super().__delitem__(i) 151 | 152 | def assemble(self): 153 | assembler = EvmDisassembler() 154 | self.bytecode = EvmBytecode(''.join(assembler.assemble(self))) 155 | self.errors = assembler.errors 156 | return self.bytecode 157 | 158 | def insert(self, index, obj): 159 | obj.previous = super().__getitem__(index).previous 160 | super().__getitem__(index).previous = obj 161 | obj.next = super().__getitem__(index) 162 | 163 | ret = super().insert(index, obj) 164 | self._fix_addresses_required = True 165 | self._fix_addresses_at_index = min(self._fix_addresses_at_index, index if index >=0 else ((len(self)-index)% len(self))) 166 | return ret 167 | 168 | def append(self, obj): 169 | prevs_item = super().__getitem__(-1) if len(self)>0 else None 170 | 171 | obj.next = None 172 | obj.previous = prevs_item 173 | 174 | if prevs_item: 175 | prevs_item.next = obj 176 | 177 | ret = super().append(obj) 178 | self._fix_addresses_required = True 179 | self._fix_addresses_at_index = min(self._fix_addresses_at_index, len(self)-1) 180 | return ret 181 | 182 | def extend(self, iterable): 183 | current_length = len(self) 184 | 185 | prevs_item = super().__getitem__(-1) if current_length else None 186 | for obj in iterable: 187 | obj.next = None 188 | obj.previous = prevs_item 189 | prevs_item = obj 190 | 191 | ret = super().extend(iterable) 192 | self._fix_addresses_required = True 193 | self._fix_addresses_at_index = min(self._fix_addresses_at_index, current_length-1) 194 | return ret 195 | 196 | def _update_instruction_addresses(self, at_index=0): 197 | # todo: only when reading from list and not on every insert 198 | if not self._fix_addresses or not self._fix_addresses_required or len(self) <=0: 199 | #print("NO FIX REQUIRED") 200 | return 201 | #print("FIX REQUIRED at %d"%at_index) 202 | assert(at_index>=0) 203 | 204 | next_pc = 0 205 | 206 | #for nr, item in enumerate(super().__iter__()): 207 | # if nr > at_index-30: 208 | # print("%d - %r"%(nr, item)) 209 | 210 | if at_index == 0: 211 | for instr in super().__iter__(): 212 | instr.address = next_pc 213 | next_pc += len(instr) 214 | elif at_index > 0: 215 | # start from previous item and update the addresses 216 | items = iter(super().__getitem__(slice(at_index - 1,None))) 217 | first_item = next(items) 218 | #print("first item: (%d) %r"%(at_index-1,first_item)) 219 | next_pc = first_item.address + len(first_item) 220 | for instr in items: 221 | instr.address = next_pc 222 | next_pc += len(instr) 223 | 224 | self._fix_addresses_at_index = len(self) - 1 225 | self._fix_addresses_required = False # we've just fixed it 226 | 227 | def get_stack_balance(self): 228 | depth = 0 229 | for instr in super().__iter__(): 230 | depth -= instr.pops 231 | depth += instr.pushes 232 | return depth 233 | 234 | def get_gas_required(self): 235 | return sum([i.gas for i in super().__iter__()]) 236 | 237 | @property 238 | def as_string(self): 239 | return '\n'.join("%s %s" % (i.name, i.operand) for i in super().__iter__()) 240 | 241 | 242 | class EvmProgram(object): 243 | """ 244 | 245 | p = EvmProgram() 246 | p.push("abcdefg") 247 | c.call(arg1, arg2, arg3, ...) 248 | c.op("JUMPDEST") 249 | 250 | # allow chaining 251 | c.op("OR").op("OR") 252 | 253 | """ 254 | 255 | def __init__(self, _registry=None, strict=False): 256 | self._registry = _registry if _registry is not None else registry.registry 257 | self._strict = strict 258 | self._program = EvmInstructions() 259 | 260 | def __getattr__(self, item): 261 | # catch all the undefined calls 262 | instr = self._registry.by_name.get(item.upper()) 263 | if not instr: 264 | raise AttributeError("Instruction %s does not exist"%item) 265 | 266 | def callback(*args, **kwargs): 267 | new_instr = instr.clone() 268 | 269 | 270 | # build args from kwargs or args; allow either kwargs or args 271 | 272 | # we'll reverse this once we push. 273 | pushargs_map = {i:None for i in range(len(instr.args))} # all args 274 | 275 | # put args in args_map 276 | for i,arg in enumerate(args): 277 | pushargs_map[i] = arg 278 | 279 | argnames_inorder = [str(a) for a in instr.args] 280 | for kwargs_argname in kwargs.keys(): 281 | # iter all kwargs and error if the key is invalid 282 | pushargs_map[argnames_inorder.index(kwargs_argname)]= kwargs[kwargs_argname] 283 | 284 | if self._strict: 285 | # require all arguments to be set 286 | if None in pushargs_map.values(): 287 | raise Exception("Strict mode - arguments %r missing for instruction: %s"%([argnames_inorder[k] for k,v in pushargs_map.items() if v is None], instr.name)) 288 | 289 | # push instructions 290 | for arg_idx in reversed(list(pushargs_map.keys())): 291 | arg = pushargs_map[arg_idx] 292 | # zero-fill args that were not provided 293 | self._program.append(self.create_push_for_data(arg if arg is not None else 0x00)) 294 | 295 | self._program.append(new_instr) 296 | return self 297 | 298 | return callback 299 | 300 | def push(self, data): 301 | self._program.append(self.create_push_for_data(data)) 302 | return self 303 | 304 | def op(self, name): 305 | self._program.append(self._registry.create_instruction(name.upper())) 306 | return self 307 | 308 | def create_push_for_data(self, data): 309 | # expect bytes but silently convert int2bytes 310 | if isinstance(data, int): 311 | data = utils.int2bytes(data) 312 | 313 | instr = self._registry.create_instruction("PUSH%d" % len(data)) 314 | instr.operand_bytes = data 315 | return instr 316 | 317 | def assemble(self): 318 | return self._program.assemble() 319 | -------------------------------------------------------------------------------- /evmdasm/instruction_registry.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Author : 4 | from .instructions import Instruction 5 | from collections import namedtuple 6 | 7 | 8 | class InstructionRegistry(object): 9 | 10 | """ 11 | Create a Registry based on a different Instruction Baseclass 12 | """ 13 | 14 | def __init__(self, instructions, _template_cls=None): 15 | """ 16 | Instruction Template based Registry 17 | 18 | creates a Registry built from Instruction() objects provided with the _template_cls parameter. 19 | Falls back to instruction.Instructions() by default. 20 | 21 | :param _template_cls: a subclass of instructions.Instruction with the same constructor arguments 22 | """ 23 | self._template_cls = Instruction if _template_cls is None else _template_cls 24 | assert(issubclass(self._template_cls, Instruction)) 25 | 26 | self._instructions, self._instructions_by_opcode, self._instructions_by_name, self._instruction = None, None, None, None 27 | self._reload(instructions) 28 | self.instruction_marks_basicblock_end = ['JUMP', 'JUMPI'] + [i.name for i in self.by_category['terminate']] 29 | 30 | def _reload(self, instructions): 31 | self._instructions = [i.clone(_template=self._template_cls) for i in instructions] 32 | self._instructions_by_opcode = {obj.opcode: obj for obj in self._instructions} 33 | self._instructions_by_name = {obj.name: obj for obj in self._instructions} 34 | self._instructions_by_category = {} 35 | self._instruction = namedtuple("Instruction", self._instructions_by_name.keys()) 36 | 37 | for instr in self._instructions: 38 | self._instructions_by_category.setdefault(instr.category, []) 39 | self._instructions_by_category[instr.category].append(instr) 40 | setattr(self._instruction, instr.name, instr) 41 | 42 | def create_instruction(self, name=None, opcode=None): 43 | assert (name is not None or opcode is not None) 44 | assert (not (name is None and opcode is None)) 45 | 46 | if name is not None: 47 | instr = self.by_name.get(name) 48 | elif opcode is not None: 49 | instr = self.by_opcode.get(opcode) 50 | if not instr: 51 | instr = self._template_cls(opcode=opcode, 52 | name="UNKNOWN_%s" % hex(opcode), 53 | description="Invalid opcode", 54 | category="unknown") 55 | else: 56 | raise Exception("name or opcode required") 57 | 58 | if not instr: 59 | raise TypeError("Failed to create instruction. Invalid Name/Opcode or Operand size") 60 | return instr.clone() 61 | 62 | @property 63 | def instruction(self): 64 | return self._instruction 65 | 66 | @property 67 | def instructions(self): 68 | return self._instructions 69 | 70 | @property 71 | def by_opcode(self): 72 | return self._instructions_by_opcode 73 | 74 | @property 75 | def by_name(self): 76 | return self._instructions_by_name 77 | 78 | @property 79 | def by_category(self): 80 | return self._instructions_by_category -------------------------------------------------------------------------------- /evmdasm/instructions.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Author : 4 | import logging 5 | import itertools 6 | from . import utils 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | class Instruction(object): 12 | """ Base Instruction class 13 | 14 | doubly linked 15 | """ 16 | 17 | def __init__(self, opcode, name, length_of_operand=0, description=None, args=None, returns=None, gas=-1, category=None, pops=None, pushes=None, fork=''): 18 | # static 19 | self._opcode, self._name, self._length_of_operand = opcode, name, length_of_operand 20 | self._gas = gas 21 | self._description = description 22 | self._args = args or [] # number of arguments the instruction takes from stack 23 | self._returns = returns or [] # number of results returned (0 or 1) 24 | self._category = category # instruction category 25 | self._pops = pops 26 | self._pushes = pushes 27 | self._fork = fork 28 | 29 | # dynamic 30 | self._opcode_bytes = (self._opcode).to_bytes(1, byteorder="big") 31 | self._operand_bytes = b'\x00'*length_of_operand # sane default 32 | self._operand = '\x00'*length_of_operand # sane default 33 | 34 | # mutable 35 | self.address = None 36 | self.next = None 37 | self.previous = None 38 | 39 | def __repr__(self): 40 | return "<%s name=%s address=%s size=%d %s>" % (self.__class__.__name__, 41 | self.name, hex(self.address) if self.address else str(self.address), self.size, 42 | "operand=%r" % self.operand if self.operand else "") 43 | 44 | def __str__(self): 45 | return "%s %s" % (self.name, "0x%s" % self.operand if self.operand else '') 46 | 47 | def __len__(self): 48 | return self.size 49 | 50 | @property 51 | def opcode(self): 52 | return self._opcode 53 | 54 | @property 55 | def name(self): 56 | return self._name 57 | 58 | @property 59 | def opcode(self): 60 | return self._opcode 61 | 62 | @property 63 | def length_of_operand(self): 64 | return self._length_of_operand 65 | 66 | @property 67 | def operand_length(self): 68 | # alias. may feel more natural 69 | return self._length_of_operand 70 | 71 | @property 72 | def gas(self): 73 | return self._gas 74 | 75 | @property 76 | def description(self): 77 | return self._description 78 | 79 | @property 80 | def args(self): 81 | return self._args 82 | 83 | @property 84 | def returns(self): 85 | return self._returns 86 | 87 | @property 88 | def category(self): 89 | return self._category 90 | 91 | @property 92 | def opcode_bytes(self): 93 | return self._opcode_bytes 94 | 95 | @property 96 | def operand_bytes(self): 97 | return self._operand_bytes 98 | 99 | @operand_bytes.setter 100 | def operand_bytes(self, b): 101 | self._operand_bytes = b 102 | self._operand = ''.join('%0.2x' % _ for _ in self._operand_bytes) 103 | return self 104 | 105 | @property 106 | def operand(self): 107 | return self._operand 108 | 109 | @operand.setter 110 | def operand(self, s): 111 | self._operand = s 112 | self._operand_bytes = utils.str_to_bytes(s) 113 | return self 114 | 115 | @property 116 | def size(self): 117 | return self.length_of_operand + 1 # opcode + operand 118 | 119 | @property 120 | def pops(self): 121 | return self._pops if self._pops is not None else len(self.args) 122 | 123 | @property 124 | def pushes(self): 125 | return self._pushes if self._pushes is not None else len(self.returns) 126 | 127 | @property 128 | def fork (self): 129 | return self._fork 130 | 131 | def clone(self, _template=None): 132 | _template = self.__class__ if _template is None else _template 133 | return _template(opcode=self.opcode, 134 | name=self.name, 135 | length_of_operand=self.length_of_operand, 136 | description=self.description, 137 | args=self.args, returns=self.returns, 138 | gas=self.gas, 139 | category=self.category, 140 | pops=self._pops, pushes=self._pushes, 141 | fork=self._fork) 142 | 143 | def consume(self, bytecode): 144 | # clone 145 | m = self.clone() 146 | # consume 147 | m.operand_bytes = bytes(_ for _ in itertools.islice(bytecode, m.length_of_operand)) 148 | return m 149 | 150 | def serialize(self): 151 | return ("%0.2x" % self.opcode) + utils.bytes_to_str(self.operand_bytes, prefix="") 152 | 153 | def skip_to(self, names): 154 | res = self.next 155 | while res: 156 | if any(res.name==name for name in names): 157 | return res 158 | res = res.next 159 | return None 160 | -------------------------------------------------------------------------------- /evmdasm/registry.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Author : 4 | from .instructions import Instruction 5 | from .instruction_registry import InstructionRegistry 6 | from . import argtypes as T 7 | 8 | INSTRUCTIONS = [ 9 | # Stop and Arithmetic Operations 10 | Instruction(opcode=0x00, name='STOP', category="terminate", gas=0, description="Halts execution."), 11 | Instruction(opcode=0x01, name='ADD', category="arithmetic", gas=3, description="Addition operation.", args=[T.Value('a'), T.Value('b')], returns=[T.Value('result')]), 12 | Instruction(opcode=0x02, name='MUL', category="arithmetic", gas=5, description="Multiplication operation.", args=[T.Value('a'), T.Value('b')], returns=[T.Value('result')]), 13 | Instruction(opcode=0x03, name='SUB', category="arithmetic", gas=3, description="Subtraction operation.", args=[T.Value('a'), T.Value('b')], returns=[T.Value('result')]), 14 | Instruction(opcode=0x04, name='DIV', category="arithmetic", gas=5, description="Integer division operation.", args=[T.Value('a'), T.Value('b')], returns=[T.Value('result')]), 15 | Instruction(opcode=0x05, name='SDIV', category="arithmetic", gas=5, description="Signed integer", args=[T.Value('a'), T.Value('b')], returns=[T.Value('result')]), 16 | Instruction(opcode=0x06, name='MOD', category="arithmetic", gas=5, description="Modulo", args=[T.Value('a'), T.Value('b')], returns=[T.Value('result')]), 17 | Instruction(opcode=0x07, name='SMOD', category="arithmetic", gas=5, description="Signed modulo", args=[T.Value('a'), T.Value('b')], returns=[T.Value('result')]), 18 | Instruction(opcode=0x08, name='ADDMOD', category="arithmetic", gas=8, description="Modulo addition operation", args=[T.Value('a'), T.Value('b'), T.Value('mod')], returns=[T.Value('result')]), 19 | Instruction(opcode=0x09, name='MULMOD', category="arithmetic", gas=8, description="Modulo multiplication operation", args=[T.Value('a'), T.Value('b'), T.Value('mod')], returns=[T.Value('result')]), 20 | Instruction(opcode=0x0a, name='EXP', category="arithmetic", gas=10, description="Exponential operation.", args=[T.Value('base'), T.Value('exponent')], returns=['result']), 21 | Instruction(opcode=0x0b, name='SIGNEXTEND', category="arithmetic", gas=5, description="Extend length of two's complement signed integer.", args=[T.Value('bits'), T.Value('num')], returns=[T.Value('result')]), 22 | 23 | # Comparison & Bitwise Logic Operations 24 | Instruction(opcode=0x10, name='LT', category="comparison", gas=3, description="Lesser-than comparison", args=[T.Value('a'), T.Value('b')], returns=[T.Bool('flag')]), 25 | Instruction(opcode=0x11, name='GT', category="comparison", gas=3, description="Greater-than comparison", args=[T.Value('a'), T.Value('b')], returns=[T.Bool('flag')]), 26 | Instruction(opcode=0x12, name='SLT', category="comparison", gas=3, description="Signed less-than comparison", args=[T.Value('a'), T.Value('b')], returns=[T.Bool('flag')]), 27 | Instruction(opcode=0x13, name='SGT', category="comparison", gas=3, description="Signed greater-than comparison", args=[T.Value('a'), T.Value('b')], returns=[T.Bool('flag')]), 28 | Instruction(opcode=0x14, name='EQ', category="comparison", gas=3, description="Equality comparison", args=[T.Value('a'), T.Value('b')], returns=[T.Bool('flag')]), 29 | Instruction(opcode=0x15, name='ISZERO', category="comparison", gas=3, description="Simple not operator", args=[T.Value('a')], returns=[T.Bool('flag')]), 30 | Instruction(opcode=0x16, name='AND', category="bitwise-logic", gas=3, description="Bitwise AND operation.", args=[T.Value('a'), T.Value('b')], returns=[T.Value('result')]), 31 | Instruction(opcode=0x17, name='OR', category="bitwise-logic",gas=3, description="Bitwise OR operation.", args=[T.Value('a'), T.Value('b')], returns=[T.Value('result')]), 32 | Instruction(opcode=0x18, name='XOR', category="bitwise-logic",gas=3, description="Bitwise XOR operation.", args=[T.Value('a'), T.Value('b')], returns=[T.Value('result')]), 33 | Instruction(opcode=0x19, name='NOT', category="bitwise-logic",gas=3, description="Bitwise NOT operation.", args=[T.Value('a'), T.Value('b')], returns=[T.Value('result')]), 34 | Instruction(opcode=0x1a, name='BYTE', category="bitwise-logic",gas=3, description="Retrieve single byte from word", args=[T.Index32('th'), T.Word('value')], returns=[T.Byte('byte')]), 35 | Instruction(opcode=0x1b, name='SHL', category="bitwise-logic", gas=3, fork="constantinople", description=" Shift left", args=[T.Index64('shift'), T.Value('value')], returns=[T.Value('result')]), 36 | Instruction(opcode=0x1c, name='SHR', category="bitwise-logic", gas=3, fork="constantinople", description=" Shift Right", args=[T.Index64('shift'), T.Value('value')], returns=[T.Value('result')]), 37 | Instruction(opcode=0x1d, name='SAR', category="bitwise-logic", gas=3, fork="constantinople", description=" Shift arithmetic right", args=[T.Index64('shift'), T.Value('value')], returns=[T.Bool('flag')]), 38 | 39 | # SHA3 40 | Instruction(opcode=0x20, name='SHA3', category="cryptographic", gas=30, description="Compute Keccak-256 hash.", args=[T.MemOffset('offset'), T.Length('size')], returns=[T.Value('sha3')]), 41 | 42 | # Environmental Information 43 | Instruction(opcode=0x30, name='ADDRESS', category="envinfo", gas=2, description="Get address of currently executing account.", returns=[T.Address('this.address')]), 44 | Instruction(opcode=0x31, name='BALANCE', category="envinfo", gas=20, description="Get balance of the given account.", args=[T.Address("address")], returns=[T.Value("this.balance")]), 45 | Instruction(opcode=0x32, name='ORIGIN', category="envinfo", gas=2, description="Get execution origination address.", returns=[T.Address("tx.origin")]), 46 | Instruction(opcode=0x33, name='CALLER', category="envinfo", gas=2, description="Get caller address.This is the address of the account that is directly responsible for this execution.", returns=[T.Address("msg.sender")]), 47 | Instruction(opcode=0x34, name='CALLVALUE', category="envinfo", gas=2, description="Get deposited value by the instruction/transaction responsible for this execution.", returns=[T.CallValue("msg.value")]), 48 | Instruction(opcode=0x35, name='CALLDATALOAD', category="envinfo", gas=3, description="Get input data of current environment.", args=[T.MemOffset('dataOffset')], returns=[T.Data("msg.data")]), 49 | Instruction(opcode=0x36, name='CALLDATASIZE', category="envinfo", gas=2, description="Get size of input data in current environment.", returns=[T.Length("msg.data.length")]), 50 | Instruction(opcode=0x37, name='CALLDATACOPY', category="envinfo", gas=3, description="Copy input data in current environment to memory. This pertains to the input data passed with the message call instruction or transaction.", args=[T.MemOffset("memOffset"), T.MemOffset("dataOffset"), T.Length("length")]), 51 | Instruction(opcode=0x38, name='CODESIZE', category="envinfo", gas=2, description="Get size of code running in current environment.", returns=[T.Length("codesize")]), 52 | Instruction(opcode=0x39, name='CODECOPY', category="envinfo", gas=3, description="Copy code running in current environment to memory.", args=[T.MemOffset("memOffset"), T.MemOffset("codeOffset"), T.Length("length")]), 53 | Instruction(opcode=0x3a, name='GASPRICE', category="envinfo", gas=2, description="Get price of gas in current environment.", returns=[T.Gas("tx.gasprice")]), 54 | Instruction(opcode=0x3b, name='EXTCODESIZE', category="envinfo", gas=20, description="Get size of an account's code.", args=[T.Address('address')], returns=["extcodesize"]), 55 | Instruction(opcode=0x3c, name='EXTCODECOPY', category="envinfo", gas=20, description="Copy an account's code to memory.", args=[T.Address("address"), T.MemOffset("memOffset"), T.MemOffset("codeOffset"), T.Length("length")]), 56 | Instruction(opcode=0x3d, name='RETURNDATASIZE', category="envinfo", gas=2, description="Push the size of the return data buffer onto the stack.", returns=["returndatasize"]), 57 | Instruction(opcode=0x3e, name='RETURNDATACOPY', category="envinfo", gas=3, description="Copy data from the return data buffer.", args=[T.MemOffset("memOffset"), T.MemOffset("dataOffset"), T.Length("length")]), 58 | Instruction(opcode=0x3f, name='EXTCODEHASH', category="envinfo", gas=400, fork="constantinople", description=" - Constantinople", args=[T.Address("address")]), 59 | 60 | 61 | # Block Information 62 | Instruction(opcode=0x40, name='BLOCKHASH', category="blockinfo", gas=20, description="Get the hash of one of the 256 most recent complete blocks.", args=[T.Index256("num")], returns=["block.blockhash"]), 63 | Instruction(opcode=0x41, name='COINBASE', category="blockinfo", gas=2, description="Get the block's beneficiary address.", returns=[T.Address("block.coinbase")]), 64 | Instruction(opcode=0x42, name='TIMESTAMP', category="blockinfo", gas=2, description="Get the block's timestamp.", returns=[T.Timestamp("block.timestamp")]), 65 | Instruction(opcode=0x43, name='NUMBER', category="blockinfo", gas=2, description="Get the block's number.", returns=[T.Value("block.number")]), 66 | Instruction(opcode=0x44, name='DIFFICULTY', category="blockinfo", gas=2, description="Get the block's difficulty.", returns=[T.Value("block.difficulty")]), 67 | Instruction(opcode=0x45, name='GASLIMIT', category="blockinfo", gas=2, description="Get the block's gas limit.", returns=[T.Gas("block.gaslimit")]), 68 | 69 | Instruction(opcode=0x46, name='CHAINID', category="blockinfo", gas=2, description="Get the chain id.", returns=[T.Gas("chain_id")]), 70 | Instruction(opcode=0x47, name='SELFBALANCE', category="blockinfo", gas=5, description="Get own balance.", returns=[T.Gas("address(this).balance")]), 71 | Instruction(opcode=0x48, name='BASEFEE', category="blockinfo", gas=2, description="Get the value of the base fee of the current block.", returns=[T.Gas("block.basefee")]), 72 | 73 | 74 | # Stack, Memory, Storage and Flow Operations 75 | Instruction(opcode=0x50, name='POP', category="stack", gas=2, description="Remove item from stack.", args=[T.Internal("#dummy")], ), 76 | # dummy is only there to indicate that there is a pop() 77 | Instruction(opcode=0x51, name='MLOAD', category="memory", gas=3, description="Load word from memory.", args=[T.MemOffset("offset")]), 78 | Instruction(opcode=0x52, name='MSTORE', category="memory", gas=3, description="Save word to memory.", args=[T.MemOffset("offset"),T.Word("value")]), 79 | Instruction(opcode=0x53, name='MSTORE8', category="memory", gas=3, description="Save byte to memory.", args=[T.MemOffset("offset"),T.Byte("value")]), 80 | Instruction(opcode=0x54, name='SLOAD', category="storage", gas=50, description="Load word from storage.", args=[T.MemOffset("loc")], returns=["value"]), 81 | Instruction(opcode=0x55, name='SSTORE', category="storage", gas=0, description="Save word to storage.", args=[T.MemOffset("loc"), T.Word("value")]), 82 | Instruction(opcode=0x56, name='JUMP', category="controlflow", gas=8, description="Alter the program counter.", args=[T.Label("evm.pc")]), 83 | Instruction(opcode=0x57, name='JUMPI', category="controlflow", gas=10, description="Conditionally alter the program counter.", args=[T.Label("evm.pc"), T.Bool("condition")]), 84 | Instruction(opcode=0x58, name='PC', category="info", gas=2, description="Get the value of the program counter prior to the increment.", returns=[T.Label("evm.pc")]), 85 | Instruction(opcode=0x59, name='MSIZE', category="memory", gas=2, description="Get the size of active memory in bytes.", returns=[T.Length("memory.length")]), 86 | Instruction(opcode=0x5a, name='GAS', category="info", gas=2, description="Get the amount of available gas, including the corresponding reduction", returns=[T.Gas("gasleft")]), 87 | Instruction(opcode=0x5b, name='JUMPDEST', category="label", gas=1, description="Mark a valid destination for jumps."), 88 | 89 | # Stack Push Operations 90 | Instruction(opcode=0x60, name='PUSH1', category="stack", gas=3, length_of_operand=0x1, description="Place 1 byte item on stack.", returns=[T.Value("item")]), 91 | Instruction(opcode=0x61, name='PUSH2', category="stack", gas=3, length_of_operand=0x2, description="Place 2-byte item on stack.", returns=[T.Value("item")]), 92 | Instruction(opcode=0x62, name='PUSH3', category="stack", gas=3, length_of_operand=0x3, description="Place 3-byte item on stack.", returns=[T.Value("item")]), 93 | Instruction(opcode=0x63, name='PUSH4', category="stack", gas=3, length_of_operand=0x4, description="Place 4-byte item on stack.", returns=[T.Value("item")]), 94 | Instruction(opcode=0x64, name='PUSH5', category="stack", gas=3, length_of_operand=0x5, description="Place 5-byte item on stack.", returns=[T.Value("item")]), 95 | Instruction(opcode=0x65, name='PUSH6', category="stack", gas=3, length_of_operand=0x6, description="Place 6-byte item on stack.", returns=[T.Value("item")]), 96 | Instruction(opcode=0x66, name='PUSH7', category="stack", gas=3, length_of_operand=0x7, description="Place 7-byte item on stack.", returns=[T.Value("item")]), 97 | Instruction(opcode=0x67, name='PUSH8', category="stack", gas=3, length_of_operand=0x8, description="Place 8-byte item on stack.", returns=[T.Value("item")]), 98 | Instruction(opcode=0x68, name='PUSH9', category="stack", gas=3, length_of_operand=0x9, description="Place 9-byte item on stack.", returns=[T.Value("item")]), 99 | Instruction(opcode=0x69, name='PUSH10', category="stack", gas=3, length_of_operand=0xa, description="Place 10-byte item on stack.", returns=[T.Value("item")]), 100 | Instruction(opcode=0x6a, name='PUSH11', category="stack", gas=3, length_of_operand=0xb, description="Place 11-byte item on stack.", returns=[T.Value("item")]), 101 | Instruction(opcode=0x6b, name='PUSH12', category="stack", gas=3, length_of_operand=0xc, description="Place 12-byte item on stack.", returns=[T.Value("item")]), 102 | Instruction(opcode=0x6c, name='PUSH13', category="stack", gas=3, length_of_operand=0xd, description="Place 13-byte item on stack.", returns=[T.Value("item")]), 103 | Instruction(opcode=0x6d, name='PUSH14', category="stack", gas=3, length_of_operand=0xe, description="Place 14-byte item on stack.", returns=[T.Value("item")]), 104 | Instruction(opcode=0x6e, name='PUSH15', category="stack", gas=3, length_of_operand=0xf, description="Place 15-byte item on stack.", returns=[T.Value("item")]), 105 | Instruction(opcode=0x6f, name='PUSH16', category="stack", gas=3, length_of_operand=0x10, description="Place 16-byte item on stack.", returns=[T.Value("item")]), 106 | Instruction(opcode=0x70, name='PUSH17', category="stack", gas=3, length_of_operand=0x11, description="Place 17-byte item on stack.", returns=[T.Value("item")]), 107 | Instruction(opcode=0x71, name='PUSH18', category="stack", gas=3, length_of_operand=0x12, description="Place 18-byte item on stack.", returns=[T.Value("item")]), 108 | Instruction(opcode=0x72, name='PUSH19', category="stack", gas=3, length_of_operand=0x13, description="Place 19-byte item on stack.", returns=[T.Value("item")]), 109 | Instruction(opcode=0x73, name='PUSH20', category="stack", gas=3, length_of_operand=0x14, description="Place 20-byte item on stack.", returns=[T.Value("item")]), 110 | Instruction(opcode=0x74, name='PUSH21', category="stack", gas=3, length_of_operand=0x15, description="Place 21-byte item on stack.", returns=[T.Value("item")]), 111 | Instruction(opcode=0x75, name='PUSH22', category="stack", gas=3, length_of_operand=0x16, description="Place 22-byte item on stack.", returns=[T.Value("item")]), 112 | Instruction(opcode=0x76, name='PUSH23', category="stack", gas=3, length_of_operand=0x17, description="Place 23-byte item on stack.", returns=[T.Value("item")]), 113 | Instruction(opcode=0x77, name='PUSH24', category="stack", gas=3, length_of_operand=0x18, description="Place 24-byte item on stack.", returns=[T.Value("item")]), 114 | Instruction(opcode=0x78, name='PUSH25', category="stack", gas=3, length_of_operand=0x19, description="Place 25-byte item on stack.", returns=[T.Value("item")]), 115 | Instruction(opcode=0x79, name='PUSH26', category="stack", gas=3, length_of_operand=0x1a, description="Place 26-byte item on stack.", returns=[T.Value("item")]), 116 | Instruction(opcode=0x7a, name='PUSH27', category="stack", gas=3, length_of_operand=0x1b, description="Place 27-byte item on stack.", returns=[T.Value("item")]), 117 | Instruction(opcode=0x7b, name='PUSH28', category="stack", gas=3, length_of_operand=0x1c, description="Place 28-byte item on stack.", returns=[T.Value("item")]), 118 | Instruction(opcode=0x7c, name='PUSH29', category="stack", gas=3, length_of_operand=0x1d, description="Place 29-byte item on stack.", returns=[T.Value("item")]), 119 | Instruction(opcode=0x7d, name='PUSH30', category="stack", gas=3, length_of_operand=0x1e, description="Place 30-byte item on stack.", returns=[T.Value("item")]), 120 | Instruction(opcode=0x7e, name='PUSH31', category="stack", gas=3, length_of_operand=0x1f, description="Place 31-byte item on stack.", returns=[T.Value("item")]), 121 | Instruction(opcode=0x7f, name='PUSH32', category="stack", gas=3, length_of_operand=0x20, description="Place 32-byte (full word) item on stack.", returns=[T.Value("item")]), 122 | 123 | # Duplication Operations 124 | Instruction(opcode=0x80, name='DUP1', category="stack", gas=3, pops=1, pushes=2, description="Duplicate 1st stack item."), 125 | Instruction(opcode=0x81, name='DUP2', category="stack", gas=3, pops=2, pushes=3, description="Duplicate 2nd stack item."), 126 | Instruction(opcode=0x82, name='DUP3', category="stack", gas=3, pops=3, pushes=4, description="Duplicate 3rd stack item."), 127 | Instruction(opcode=0x83, name='DUP4', category="stack", gas=3, pops=4, pushes=5, description="Duplicate 4th stack item."), 128 | Instruction(opcode=0x84, name='DUP5', category="stack", gas=3, pops=5, pushes=6, description="Duplicate 5th stack item."), 129 | Instruction(opcode=0x85, name='DUP6', category="stack", gas=3, pops=6, pushes=7, description="Duplicate 6th stack item."), 130 | Instruction(opcode=0x86, name='DUP7', category="stack", gas=3, pops=7, pushes=8, description="Duplicate 7th stack item."), 131 | Instruction(opcode=0x87, name='DUP8', category="stack", gas=3, pops=8, pushes=9, description="Duplicate 8th stack item."), 132 | Instruction(opcode=0x88, name='DUP9', category="stack", gas=3, pops=9, pushes=10, description="Duplicate 9th stack item."), 133 | Instruction(opcode=0x89, name='DUP10', category="stack", gas=3, pops=10, pushes=11, description="Duplicate 10th stack item."), 134 | Instruction(opcode=0x8a, name='DUP11', category="stack", gas=3, pops=11, pushes=12, description="Duplicate 11th stack item."), 135 | Instruction(opcode=0x8b, name='DUP12', category="stack", gas=3, pops=12, pushes=13, description="Duplicate 12th stack item."), 136 | Instruction(opcode=0x8c, name='DUP13', category="stack", gas=3, pops=13, pushes=14, description="Duplicate 13th stack item."), 137 | Instruction(opcode=0x8d, name='DUP14', category="stack", gas=3, pops=14, pushes=15, description="Duplicate 14th stack item."), 138 | Instruction(opcode=0x8e, name='DUP15', category="stack", gas=3, pops=15, pushes=16, description="Duplicate 15th stack item."), 139 | Instruction(opcode=0x8f, name='DUP16', category="stack", gas=3, pops=16, pushes=17, description="Duplicate 16th stack item."), 140 | 141 | # Exchange Operations 142 | Instruction(opcode=0x90, name='SWAP1', category="stack", gas=3, pops=2, pushes=2, description="Exchange 1st and 2nd stack items."), 143 | Instruction(opcode=0x91, name='SWAP2', category="stack", gas=3, pops=3, pushes=3, description="Exchange 1st and 3rd stack items."), 144 | Instruction(opcode=0x92, name='SWAP3', category="stack", gas=3, pops=4, pushes=3, description="Exchange 1st and 4th stack items."), 145 | Instruction(opcode=0x93, name='SWAP4', category="stack", gas=3, pops=5, pushes=4, description="Exchange 1st and 5th stack items."), 146 | Instruction(opcode=0x94, name='SWAP5', category="stack", gas=3, pops=6, pushes=5, description="Exchange 1st and 6th stack items."), 147 | Instruction(opcode=0x95, name='SWAP6', category="stack", gas=3, pops=7, pushes=6, description="Exchange 1st and 7th stack items."), 148 | Instruction(opcode=0x96, name='SWAP7', category="stack", gas=3, pops=8, pushes=7, description="Exchange 1st and 8th stack items."), 149 | Instruction(opcode=0x97, name='SWAP8', category="stack", gas=3, pops=9, pushes=9, description="Exchange 1st and 9th stack items."), 150 | Instruction(opcode=0x98, name='SWAP9', category="stack", gas=3, pops=10, pushes=10, description="Exchange 1st and 10th stack items."), 151 | Instruction(opcode=0x99, name='SWAP10', category="stack", gas=3, pops=11, pushes=11, description="Exchange 1st and 11th stack items."), 152 | Instruction(opcode=0x9a, name='SWAP11', category="stack", gas=3, pops=12, pushes=12, description="Exchange 1st and 12th stack items."), 153 | Instruction(opcode=0x9b, name='SWAP12', category="stack", gas=3, pops=13, pushes=13, description="Exchange 1st and 13th stack items."), 154 | Instruction(opcode=0x9c, name='SWAP13', category="stack", gas=3, pops=14, pushes=14, description="Exchange 1st and 14th stack items."), 155 | Instruction(opcode=0x9d, name='SWAP14', category="stack", gas=3, pops=15, pushes=15, description="Exchange 1st and 15th stack items."), 156 | Instruction(opcode=0x9e, name='SWAP15', category="stack", gas=3, pops=16, pushes=16, description="Exchange 1st and 16th stack items."), 157 | Instruction(opcode=0x9f, name='SWAP16', category="stack", gas=3, pops=17, pushes=17, description="Exchange 1st and 17th stack items."), 158 | 159 | # Logging Operations 160 | Instruction(opcode=0xa0, name='LOG0', category="event", gas=375, description="Append log record with no topics.", args=[T.MemOffset("start"), T.Length("size")]), 161 | Instruction(opcode=0xa1, name='LOG1', category="event", gas=750, description="Append log record with one topic.", args=[T.MemOffset("start"), T.Length("size"), T.Value("topic1")]), 162 | Instruction(opcode=0xa2, name='LOG2', category="event", gas=1125, description="Append log record with two topics.", args=[T.MemOffset("start"), T.Length("size"), T.Value("topic1"), T.Value("topic2")]), 163 | Instruction(opcode=0xa3, name='LOG3', category="event", gas=1500, description="Append log record with three topics.", args=[T.MemOffset("start"), T.Length("size"), T.Value("topic1"), T.Value("topic2"), T.Value("topic3")]), 164 | Instruction(opcode=0xa4, name='LOG4', category="event", gas=1875, description="Append log record with four topics.", args=[T.MemOffset("start"), T.Length("size"), T.Value("topic1"), T.Value("topic2"), T.Value("topic3"), T.Value("topic4")]), 165 | 166 | # unofficial opcodes used for parsing. 167 | Instruction(opcode=0xb0, name='UNOFFICIAL_PUSH', category="unofficial", description="unofficial opcodes used for parsing."), 168 | Instruction(opcode=0xb1, name='UNOFFICIAL_DUP', category="unofficial", description="unofficial opcodes used for parsing."), 169 | Instruction(opcode=0xb2, name='UNOFFICIAL_SWAP', category="unofficial", description="unofficial opcodes used for parsing."), 170 | 171 | # System Operations 172 | Instruction(opcode=0xf0, name='CREATE', category="system", gas=32000, description="Create a new account with associated code.", args=[T.CallValue("value"), T.MemOffset("offset"), T.Length("size")]), 173 | Instruction(opcode=0xf1, name='CALL', category="system", gas=40, description="Message-call into an account.", args=[T.Gas("gas"), T.Address("address"), T.CallValue("value"), T.MemOffset("inOffset"), T.Length("inSize"), T.MemOffset("retOffset"), T.Length("retSize")]), 174 | Instruction(opcode=0xf2, name='CALLCODE', category="system", gas=40, description="Message-call into this account with alternative account's code.", args=[T.Gas("gas"), T.Address("address"), T.CallValue("value"), T.MemOffset("inOffset"), T.Length("inSize"), T.MemOffset("retOffset"), T.Length("retSize")]), 175 | Instruction(opcode=0xf3, name='RETURN', category="terminate", gas=0, description="Halt execution returning output data.", args=[T.MemOffset("offset"), T.Length("size")]), 176 | Instruction(opcode=0xf4, name='DELEGATECALL', category="system", gas=40, description="Similar to CALLCODE except that it propagates the sender and value from the parent scope to the child scope", args=[T.Gas("gas"), T.Address("address"), T.MemOffset("inOffset"), T.Length("inSize"), T.MemOffset("retOffset"), T.Length("retSize")]), 177 | Instruction(opcode=0xf5, name='CREATE2', category="system", gas=32000, fork="constantinople", description="Create a new account with associated code. (Constantinople)", args=[T.Value("endowment"), T.MemOffset("offset"), T.Length("size"), T.Value("salt")]), 178 | 179 | # Newer opcode 180 | Instruction(opcode=0xfa, name='STATICCALL', category="system", gas=40, description='Call another contract (or itself) while disallowing any modifications to the state during the call.', args=[T.Gas("gas"), T.Address("address"), T.MemOffset("inOffset"), T.Length("inSize"), T.MemOffset("retOffset"), T.Length("retSize")]), 181 | Instruction(opcode=0xfd, name='REVERT', category="terminate", gas=0, description='throw an error', args=[T.MemOffset("offset"), T.Length("size")]), 182 | 183 | # Halt Execution, Mark for deletion 184 | Instruction(opcode=0xff, name='SELFDESTRUCT', category="terminate", gas=0, description="Halt execution and register account for later deletion.", args=[T.Address("address")]), 185 | ] 186 | 187 | ''' 188 | ///////////////////////////////////////////////// 189 | // 190 | // Here be dragons. Thou art forewarned 191 | // 192 | // 193 | TODO: deduplicate the interfaces 194 | ''' 195 | 196 | # offer an InstructionRegistry using the default instructions.Instruction 197 | registry = InstructionRegistry(instructions=INSTRUCTIONS) 198 | 199 | # rebuild the registry with our extended Instruction class. (clone with our class as template) 200 | INSTRUCTIONS_BY_OPCODE = registry.by_opcode 201 | INSTRUCTIONS_BY_NAME = registry.by_name 202 | INSTRUCTIONS_BY_CATEGORY = registry.by_category 203 | instruction = registry.instruction 204 | INSTRUCTION_MARKS_BASICBLOCK_END = registry.instruction_marks_basicblock_end 205 | create_instruction = registry.create_instruction 206 | 207 | 208 | 209 | 210 | -------------------------------------------------------------------------------- /evmdasm/utils.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Author : 4 | 5 | import string 6 | import binascii 7 | 8 | 9 | def str_to_bytes(s): 10 | """ 11 | Convert 0xHexString to bytes 12 | :param s: 0x hexstring 13 | :return: byte sequence 14 | """ 15 | try: 16 | return bytes.fromhex(s.replace("0x", "")) 17 | except (NameError, AttributeError): 18 | return s.decode("hex") 19 | 20 | 21 | def bytes_to_str(s, prefix="0x"): 22 | return "%s%s" % (prefix,binascii.hexlify(s).decode("utf-8")) 23 | 24 | 25 | def strip_0x_prefix(s): 26 | if not s.startswith("0x"): 27 | return s 28 | 29 | return s[2:] # strip 0x 30 | 31 | 32 | def is_hexstring(s): 33 | hex_digits = set(string.hexdigits) 34 | # if s is long, then it is faster to check against a set 35 | return all(c in hex_digits for c in s) 36 | 37 | def int2bytes(i): 38 | hex_string = '%x' % i 39 | n = len(hex_string) 40 | return binascii.unhexlify(hex_string.zfill(n + (n & 1))) -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | from setuptools import setup, find_packages 6 | 7 | 8 | def read(fname): 9 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 10 | 11 | 12 | version = "0.1.10" 13 | name = "evmdasm" 14 | 15 | setup( 16 | name=name, 17 | version=version, 18 | packages=find_packages(), 19 | author="tintinweb", 20 | author_email="tintinweb@oststrom.com", 21 | description=( 22 | "A lightweight ethereum evm bytecode asm instruction registry and disassembler library."), 23 | license="GPLv2", 24 | keywords=["evmdasm", "ethereum", "blockchain", "evm", "disassembler"], 25 | url="https://github.com/tintinweb/%s"%name, 26 | download_url="https://github.com/tintinweb/%s/tarball/v%s"%(name,version), 27 | #python setup.py register -r https://testpypi.python.org/pypi 28 | long_description=read("README.md") if os.path.isfile("README.md") else "", 29 | long_description_content_type='text/markdown', 30 | install_requires=[""], 31 | #package_data={}, 32 | #extras_require={}, 33 | ) 34 | -------------------------------------------------------------------------------- /tests/test_disassembler.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Author : 4 | 5 | import unittest 6 | import random 7 | 8 | from evmdasm import EvmDisassembler, EvmBytecode, EvmInstructions, Instruction, registry 9 | import evmdasm.utils as utils 10 | 11 | 12 | class EvmInstructionTest(unittest.TestCase): 13 | 14 | def setUp(self): 15 | self.testcode = '606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806319e30bc7146100775780635bd74afe146101055780639df211541461017f578063b1d131bf146101ad575b6000366001919061007492919061042d565b50005b341561008257600080fd5b61008a610202565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100ca5780820151818401526020810190506100af565b50505050905090810190601f1680156100f75780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61017d600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001908201803590602001908080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050919080359060200190919050506102a0565b005b6101ab600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190505061038d565b005b34156101b857600080fd5b6101c0610408565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60018054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156102985780601f1061026d57610100808354040283529160200191610298565b820191906000526020600020905b81548152906001019060200180831161027b57829003601f168201915b505050505081565b3373ffffffffffffffffffffffffffffffffffffffff166000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415156102fb57600080fd5b8273ffffffffffffffffffffffffffffffffffffffff16818360405180828051906020019080838360005b83811015610341578082015181840152602081019050610326565b50505050905090810190601f16801561036e5780820380516001836020036101000a031916815260200191505b5091505060006040518083038185876187965a03f19250505050505050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051600060405180830381858888f19350505050151561040557600080fd5b50565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061046e57803560ff191683800117855561049c565b8280016001018555821561049c579182015b8281111561049b578235825591602001919060010190610480565b5b5090506104a991906104ad565b5090565b6104cf91905b808211156104cb5760008160009055506001016104b3565b5090565b905600a165627a7a723058202592c848fd2bdbf19b6558815a8c0a67519b4ad552eb001c92109a188ef215950029' 16 | self.evmcode = EvmBytecode("0x%s" % self.testcode) 17 | 18 | 19 | def _check_address_linear(self, instructions): 20 | assert(len(instructions)>0) 21 | 22 | pc = instructions[0].address 23 | for instr in instructions: 24 | self.assertEqual(instr.address, pc) 25 | pc += len(instr) 26 | 27 | def _check_address_linear_getitem(self, instructions): 28 | assert(len(instructions)>0) 29 | 30 | pc = instructions[0].address 31 | for idx in range(len(instructions)): 32 | instr = instructions[idx] 33 | self.assertEqual(instr.address, pc) 34 | pc += len(instr) 35 | 36 | def test_address_linear(self): 37 | self._check_address_linear(self.evmcode.disassemble()) 38 | 39 | def test_append(self): 40 | instructions = self.evmcode.disassemble() 41 | for _ in range(4000): 42 | instructions.append(registry.create_instruction(name="JUMPDEST")) 43 | self._check_address_linear(instructions) 44 | 45 | def test_append_check_end(self): 46 | instructions = self.evmcode.disassemble() 47 | for _ in range(4000): 48 | instructions.append(registry.create_instruction(name="JUMPDEST")) 49 | self._check_address_linear(instructions) 50 | self._check_address_linear(instructions) 51 | 52 | def test_append_check_end_getitem(self): 53 | instructions = self.evmcode.disassemble() 54 | for _ in range(4000): 55 | instructions.append(registry.create_instruction(name="JUMPDEST")) 56 | self._check_address_linear_getitem(instructions) 57 | self._check_address_linear_getitem(instructions) 58 | 59 | def test_append_check_end_no_fix_address(self): 60 | instructions = self.evmcode.disassemble() 61 | instructions._fix_addresses = False 62 | 63 | for _ in range(4000): 64 | instructions.append(registry.create_instruction(name="JUMPDEST")) 65 | 66 | error = False 67 | pc = instructions[0].address 68 | for instr in instructions: 69 | if not instr.address == pc: 70 | error=True 71 | break 72 | pc += len(instr) 73 | self.assertTrue(error) 74 | 75 | def test_insert(self): 76 | instructions = self.evmcode.disassemble() 77 | 78 | instructions.insert(0, registry.create_instruction(name="JUMPDEST")) 79 | self._check_address_linear(instructions) 80 | 81 | for idx in range(len(instructions)): 82 | instructions.insert(-idx, registry.create_instruction(name="JUMPDEST")) 83 | self._check_address_linear(instructions) 84 | 85 | for _ in range(len(instructions)): 86 | idx = random.randint(0, len(instructions)-1) 87 | instructions.insert(idx, registry.create_instruction(name="JUMPDEST")) 88 | self._check_address_linear(instructions) 89 | 90 | def test_insert_check_end(self): 91 | instructions = self.evmcode.disassemble() 92 | 93 | instructions.insert(0, registry.create_instruction(name="JUMPDEST")) 94 | 95 | for idx in range(len(instructions)): 96 | instructions.insert(-idx, registry.create_instruction(name="JUMPDEST")) 97 | self._check_address_linear(instructions) 98 | 99 | for _ in range(len(instructions)): 100 | idx = random.randint(0, len(instructions)-1) 101 | instructions.insert(idx, registry.create_instruction(name="JUMPDEST")) 102 | self._check_address_linear(instructions) 103 | 104 | def test_del(self): 105 | instructions = self.evmcode.disassemble() 106 | 107 | while True: 108 | length = len(instructions) 109 | del_idx = random.randint(0, length-1) 110 | 111 | del(instructions[del_idx]) 112 | 113 | if len(instructions) <= 0: 114 | break 115 | 116 | self._check_address_linear(instructions) 117 | 118 | assert(len(instructions) == 0) 119 | 120 | def test_del_check_every_10_items(self): 121 | instructions = self.evmcode.disassemble() 122 | 123 | while True: 124 | length = len(instructions) 125 | del_idx = random.randint(0, length-1) 126 | del (instructions[del_idx]) 127 | 128 | if length % 10 == 0: 129 | self._check_address_linear(instructions) 130 | 131 | if length <= 1: 132 | break 133 | 134 | assert(len(instructions)==0) 135 | 136 | 137 | 138 | class EvmBytecodeTest(unittest.TestCase): 139 | 140 | def setUp(self): 141 | self.testcode = '606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806319e30bc7146100775780635bd74afe146101055780639df211541461017f578063b1d131bf146101ad575b6000366001919061007492919061042d565b50005b341561008257600080fd5b61008a610202565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100ca5780820151818401526020810190506100af565b50505050905090810190601f1680156100f75780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61017d600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001908201803590602001908080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050919080359060200190919050506102a0565b005b6101ab600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190505061038d565b005b34156101b857600080fd5b6101c0610408565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60018054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156102985780601f1061026d57610100808354040283529160200191610298565b820191906000526020600020905b81548152906001019060200180831161027b57829003601f168201915b505050505081565b3373ffffffffffffffffffffffffffffffffffffffff166000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415156102fb57600080fd5b8273ffffffffffffffffffffffffffffffffffffffff16818360405180828051906020019080838360005b83811015610341578082015181840152602081019050610326565b50505050905090810190601f16801561036e5780820380516001836020036101000a031916815260200191505b5091505060006040518083038185876187965a03f19250505050505050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051600060405180830381858888f19350505050151561040557600080fd5b50565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061046e57803560ff191683800117855561049c565b8280016001018555821561049c579182015b8281111561049b578235825591602001919060010190610480565b5b5090506104a991906104ad565b5090565b6104cf91905b808211156104cb5760008160009055506001016104b3565b5090565b905600a165627a7a723058202592c848fd2bdbf19b6558815a8c0a67519b4ad552eb001c92109a188ef215950029' 142 | #print(utils.str_to_bytes(self.testcode)) 143 | self.testcode_bytes = b'```@R`\x046\x10a\x00bW`\x005|\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90\x04c\xff\xff\xff\xff\x16\x80c\x19\xe3\x0b\xc7\x14a\x00wW\x80c[\xd7J\xfe\x14a\x01\x05W\x80c\x9d\xf2\x11T\x14a\x01\x7fW\x80c\xb1\xd11\xbf\x14a\x01\xadW[`\x006`\x01\x91\x90a\x00t\x92\x91\x90a\x04-V[P\x00[4\x15a\x00\x82W`\x00\x80\xfd[a\x00\x8aa\x02\x02V[`@Q\x80\x80` \x01\x82\x81\x03\x82R\x83\x81\x81Q\x81R` \x01\x91P\x80Q\x90` \x01\x90\x80\x83\x83`\x00[\x83\x81\x10\x15a\x00\xcaW\x80\x82\x01Q\x81\x84\x01R` \x81\x01\x90Pa\x00\xafV[PPPP\x90P\x90\x81\x01\x90`\x1f\x16\x80\x15a\x00\xf7W\x80\x82\x03\x80Q`\x01\x83` \x03a\x01\x00\n\x03\x19\x16\x81R` \x01\x91P[P\x92PPP`@Q\x80\x91\x03\x90\xf3[a\x01}`\x04\x80\x805s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x16\x90` \x01\x90\x91\x90\x805\x90` \x01\x90\x82\x01\x805\x90` \x01\x90\x80\x80`\x1f\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x93\x92\x91\x90\x81\x81R` \x01\x83\x83\x80\x82\x847\x82\x01\x91PPPPPP\x91\x90\x805\x90` \x01\x90\x91\x90PPa\x02\xa0V[\x00[a\x01\xab`\x04\x80\x805s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x16\x90` \x01\x90\x91\x90PPa\x03\x8dV[\x00[4\x15a\x01\xb8W`\x00\x80\xfd[a\x01\xc0a\x04\x08V[`@Q\x80\x82s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x16s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x16\x81R` \x01\x91PP`@Q\x80\x91\x03\x90\xf3[`\x01\x80T`\x01\x81`\x01\x16\x15a\x01\x00\x02\x03\x16`\x02\x90\x04\x80`\x1f\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x92\x91\x90\x81\x81R` \x01\x82\x80T`\x01\x81`\x01\x16\x15a\x01\x00\x02\x03\x16`\x02\x90\x04\x80\x15a\x02\x98W\x80`\x1f\x10a\x02mWa\x01\x00\x80\x83T\x04\x02\x83R\x91` \x01\x91a\x02\x98V[\x82\x01\x91\x90`\x00R` `\x00 \x90[\x81T\x81R\x90`\x01\x01\x90` \x01\x80\x83\x11a\x02{W\x82\x90\x03`\x1f\x16\x82\x01\x91[PPPPP\x81V[3s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x16`\x00\x80\x90T\x90a\x01\x00\n\x90\x04s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x16s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x16\x14\x15\x15a\x02\xfbW`\x00\x80\xfd[\x82s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x16\x81\x83`@Q\x80\x82\x80Q\x90` \x01\x90\x80\x83\x83`\x00[\x83\x81\x10\x15a\x03AW\x80\x82\x01Q\x81\x84\x01R` \x81\x01\x90Pa\x03&V[PPPP\x90P\x90\x81\x01\x90`\x1f\x16\x80\x15a\x03nW\x80\x82\x03\x80Q`\x01\x83` \x03a\x01\x00\n\x03\x19\x16\x81R` \x01\x91P[P\x91PP`\x00`@Q\x80\x83\x03\x81\x85\x87a\x87\x96Z\x03\xf1\x92PPPPPPPV[`\x00\x80\x90T\x90a\x01\x00\n\x90\x04s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x16s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x16a\x08\xfc0s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x161\x90\x81\x15\x02\x90`@Q`\x00`@Q\x80\x83\x03\x81\x85\x88\x88\xf1\x93PPPP\x15\x15a\x04\x05W`\x00\x80\xfd[PV[`\x00\x80\x90T\x90a\x01\x00\n\x90\x04s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x16\x81V[\x82\x80T`\x01\x81`\x01\x16\x15a\x01\x00\x02\x03\x16`\x02\x90\x04\x90`\x00R` `\x00 \x90`\x1f\x01` \x90\x04\x81\x01\x92\x82`\x1f\x10a\x04nW\x805`\xff\x19\x16\x83\x80\x01\x17\x85Ua\x04\x9cV[\x82\x80\x01`\x01\x01\x85U\x82\x15a\x04\x9cW\x91\x82\x01[\x82\x81\x11\x15a\x04\x9bW\x825\x82U\x91` \x01\x91\x90`\x01\x01\x90a\x04\x80V[[P\x90Pa\x04\xa9\x91\x90a\x04\xadV[P\x90V[a\x04\xcf\x91\x90[\x80\x82\x11\x15a\x04\xcbW`\x00\x81`\x00\x90UP`\x01\x01a\x04\xb3V[P\x90V[\x90V\x00\xa1ebzzr0X %\x92\xc8H\xfd+\xdb\xf1\x9beX\x81Z\x8c\ngQ\x9bJ\xd5R\xeb\x00\x1c\x92\x10\x9a\x18\x8e\xf2\x15\x95\x00)' 144 | 145 | def test_disassembler_noerror(self): 146 | evmcode = EvmBytecode(self.testcode) 147 | self.assertFalse(evmcode.disassemble().errors) 148 | self.assertFalse(evmcode.disassemble().assemble().errors) 149 | 150 | def test_disassembler_0xhexstr(self): 151 | evmcode = EvmBytecode("0x%s" % self.testcode) 152 | self.assertEqual(evmcode.bytecode, self.testcode) 153 | self.assertIsInstance(evmcode.disassemble(), EvmInstructions) 154 | self.assertIsInstance(evmcode.disassemble().assemble(), EvmBytecode) 155 | self.assertEqual(utils.strip_0x_prefix(self.testcode), evmcode.disassemble().assemble().as_hexstring) 156 | 157 | def test_disassembler_hexstr(self): 158 | evmcode = EvmBytecode(self.testcode) 159 | self.assertEqual(evmcode.bytecode, self.testcode) 160 | self.assertIsInstance(evmcode.disassemble(), EvmInstructions) 161 | self.assertIsInstance(evmcode.disassemble().assemble(), EvmBytecode) 162 | self.assertEqual(self.testcode, evmcode.disassemble().assemble().as_hexstring) 163 | 164 | def test_disassembler_bytes(self): 165 | evmcode = EvmBytecode(self.testcode_bytes) 166 | self.assertEqual(self.testcode, evmcode.bytecode) 167 | self.assertIsInstance(evmcode.disassemble(), EvmInstructions) 168 | self.assertIsInstance(evmcode.disassemble().assemble(), EvmBytecode) 169 | self.assertEqual(self.testcode, evmcode.disassemble().assemble().as_hexstring) 170 | 171 | def test_instructions_string(self): 172 | evmcode = EvmBytecode(self.testcode_bytes) 173 | evmcode_listing = evmcode.disassemble().as_string 174 | self.assertIn("PUSH1 60\n", evmcode_listing) 175 | self.assertIn("\nUNKNOWN_0x29", evmcode_listing) 176 | 177 | 178 | class EvmDisassemblerTest(unittest.TestCase): 179 | 180 | def setUp(self): 181 | self.testcode = '606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806319e30bc7146100775780635bd74afe146101055780639df211541461017f578063b1d131bf146101ad575b6000366001919061007492919061042d565b50005b341561008257600080fd5b61008a610202565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100ca5780820151818401526020810190506100af565b50505050905090810190601f1680156100f75780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61017d600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001908201803590602001908080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050919080359060200190919050506102a0565b005b6101ab600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190505061038d565b005b34156101b857600080fd5b6101c0610408565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60018054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156102985780601f1061026d57610100808354040283529160200191610298565b820191906000526020600020905b81548152906001019060200180831161027b57829003601f168201915b505050505081565b3373ffffffffffffffffffffffffffffffffffffffff166000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415156102fb57600080fd5b8273ffffffffffffffffffffffffffffffffffffffff16818360405180828051906020019080838360005b83811015610341578082015181840152602081019050610326565b50505050905090810190601f16801561036e5780820380516001836020036101000a031916815260200191505b5091505060006040518083038185876187965a03f19250505050505050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051600060405180830381858888f19350505050151561040557600080fd5b50565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061046e57803560ff191683800117855561049c565b8280016001018555821561049c579182015b8281111561049b578235825591602001919060010190610480565b5b5090506104a991906104ad565b5090565b6104cf91905b808211156104cb5760008160009055506001016104b3565b5090565b905600a165627a7a723058202592c848fd2bdbf19b6558815a8c0a67519b4ad552eb001c92109a188ef215950029' 182 | 183 | def test_disassembler(self): 184 | disassembler = EvmDisassembler() 185 | instruction_list = list(disassembler.disassemble(self.testcode)) 186 | self.assertTrue(instruction_list) 187 | self.assertIsInstance(instruction_list[0], Instruction) 188 | self.assertIsInstance(instruction_list[-1], Instruction) 189 | self.assertEqual(instruction_list[0].name, "PUSH1") 190 | 191 | self.assertEqual(''.join(disassembler.assemble(instruction_list)), self.testcode) 192 | 193 | def test_disassembler_0xinput(self): 194 | disassembler = EvmDisassembler() 195 | instruction_list = list(disassembler.disassemble("0x"+self.testcode)) 196 | self.assertTrue(instruction_list) 197 | self.assertIsInstance(instruction_list[0], Instruction) 198 | self.assertIsInstance(instruction_list[-1], Instruction) 199 | self.assertEqual(instruction_list[0].name, "PUSH1") 200 | 201 | self.assertEqual(''.join(disassembler.assemble(instruction_list)), self.testcode) 202 | 203 | 204 | class MyInstruction(Instruction): 205 | 206 | def __init__(self, opcode, name, length_of_operand=0, description=None, args=None, returns=None, gas=-1, 207 | category=None, pops=None, pushes=None, fork=None): 208 | super().__init__(opcode=opcode, name=name, 209 | length_of_operand=length_of_operand, 210 | description=description, 211 | args=args, returns=returns, 212 | gas=gas, category=category, 213 | pops=pops, pushes=pushes, fork=fork) 214 | 215 | # additional attribs 216 | self.annotations = [] 217 | self.xrefs = set([]) 218 | self.jumpto = None 219 | self.basicblock = None 220 | 221 | 222 | class EvmDisassemblerCustomTest(unittest.TestCase): 223 | 224 | def setUp(self): 225 | self.registry = registry.InstructionRegistry(instructions=registry.INSTRUCTIONS, _template_cls=MyInstruction) 226 | self.testcode = '606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806319e30bc7146100775780635bd74afe146101055780639df211541461017f578063b1d131bf146101ad575b6000366001919061007492919061042d565b50005b341561008257600080fd5b61008a610202565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100ca5780820151818401526020810190506100af565b50505050905090810190601f1680156100f75780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61017d600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001908201803590602001908080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050919080359060200190919050506102a0565b005b6101ab600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190505061038d565b005b34156101b857600080fd5b6101c0610408565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60018054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156102985780601f1061026d57610100808354040283529160200191610298565b820191906000526020600020905b81548152906001019060200180831161027b57829003601f168201915b505050505081565b3373ffffffffffffffffffffffffffffffffffffffff166000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415156102fb57600080fd5b8273ffffffffffffffffffffffffffffffffffffffff16818360405180828051906020019080838360005b83811015610341578082015181840152602081019050610326565b50505050905090810190601f16801561036e5780820380516001836020036101000a031916815260200191505b5091505060006040518083038185876187965a03f19250505050505050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051600060405180830381858888f19350505050151561040557600080fd5b50565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061046e57803560ff191683800117855561049c565b8280016001018555821561049c579182015b8281111561049b578235825591602001919060010190610480565b5b5090506104a991906104ad565b5090565b6104cf91905b808211156104cb5760008160009055506001016104b3565b5090565b905600a165627a7a723058202592c848fd2bdbf19b6558815a8c0a67519b4ad552eb001c92109a188ef215950029' 227 | 228 | def test_disassembler(self): 229 | disassembler = EvmDisassembler(_registry=self.registry) 230 | instruction_list = list(disassembler.disassemble(self.testcode)) 231 | self.assertTrue(instruction_list) 232 | self.assertIsInstance(instruction_list[0], Instruction) 233 | self.assertIsInstance(instruction_list[-1], Instruction) 234 | self.assertEqual(instruction_list[0].name, "PUSH1") 235 | 236 | self.assertEqual(''.join(disassembler.assemble(instruction_list)), self.testcode) 237 | 238 | for instr in instruction_list: 239 | self.assertIsInstance(instr, MyInstruction) 240 | 241 | def test_disassembler_0xinput(self): 242 | disassembler = EvmDisassembler() 243 | instruction_list = list(disassembler.disassemble("0x"+self.testcode)) 244 | self.assertTrue(instruction_list) 245 | self.assertIsInstance(instruction_list[0], Instruction) 246 | self.assertIsInstance(instruction_list[-1], Instruction) 247 | self.assertEqual(instruction_list[0].name, "PUSH1") 248 | 249 | self.assertEqual(''.join(disassembler.assemble(instruction_list)), self.testcode) 250 | 251 | -------------------------------------------------------------------------------- /tests/test_evmprogram.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Author : 4 | 5 | import unittest 6 | import random 7 | 8 | from evmdasm import EvmInstructions, EvmProgram, registry, Instruction 9 | 10 | 11 | 12 | class EvmInstructionTest(unittest.TestCase): 13 | 14 | def setUp(self): 15 | pass 16 | 17 | def test_assemble(self): 18 | p = EvmProgram() 19 | p.push(1) 20 | p.push(0x0101) 21 | p.op("JUMPDEST") 22 | p.push(0xc0fefe) 23 | 24 | assembled = p.assemble() 25 | 26 | self.assertEqual(assembled.as_hexstring, "60016101015b62c0fefe") 27 | 28 | expect = [("PUSH1","01"), 29 | ("PUSH2","0101"), 30 | ("JUMPDEST",""), 31 | ("PUSH3","c0fefe")] 32 | 33 | for idx,instr in enumerate(p.assemble().disassemble()): 34 | self.assertEqual(instr.name, expect[idx][0]) 35 | self.assertEqual(instr.operand, expect[idx][1]) 36 | 37 | def test_assemble_call_args(self): 38 | p = EvmProgram() 39 | p.push(1) 40 | p.push(0x0101) 41 | p.op("JUMPDEST") 42 | p.push(0xc0fefe) 43 | #T.Gas("gas"), T.Address("address"), T.CallValue("value"), T.MemOffset("inOffset"), T.Length("inSize"), T.MemOffset("retOffset"), T.Length("retSize") 44 | p.call(1,2,3,4,5,6,7) 45 | 46 | assembled = p.assemble() 47 | self.assertEqual(assembled.as_hexstring, "60016101015b62c0fefe6007600660056004600360026001f1") 48 | 49 | self.assertEqual(assembled.disassemble().as_string.strip(), """PUSH1 01 50 | PUSH2 0101 51 | JUMPDEST 52 | PUSH3 c0fefe 53 | PUSH1 07 54 | PUSH1 06 55 | PUSH1 05 56 | PUSH1 04 57 | PUSH1 03 58 | PUSH1 02 59 | PUSH1 01 60 | CALL""".strip()) 61 | 62 | def test_assemble_call_kargs(self): 63 | p = EvmProgram() 64 | p.push(1) 65 | p.push(0x0101) 66 | p.op("JUMPDEST") 67 | p.push(0xc0fefe) 68 | # T.Gas("gas"), T.Address("address"), T.CallValue("value"), T.MemOffset("inOffset"), T.Length("inSize"), T.MemOffset("retOffset"), T.Length("retSize") 69 | p.call(gas=1, address=2, value=3, inOffset=4, inSize=5, retOffset=6, retSize=7) 70 | 71 | assembled = p.assemble() 72 | self.assertEqual(assembled.as_hexstring, "60016101015b62c0fefe6007600660056004600360026001f1") 73 | 74 | self.assertEqual(assembled.disassemble().as_string.strip(), """PUSH1 01 75 | PUSH2 0101 76 | JUMPDEST 77 | PUSH3 c0fefe 78 | PUSH1 07 79 | PUSH1 06 80 | PUSH1 05 81 | PUSH1 04 82 | PUSH1 03 83 | PUSH1 02 84 | PUSH1 01 85 | CALL""".strip()) 86 | 87 | def test_assemble_call_kwargs_unsort(self): 88 | p = EvmProgram() 89 | p.push(1) 90 | p.push(0x0101) 91 | p.op("JUMPDEST") 92 | p.push(0xc0fefe) 93 | # T.Gas("gas"), T.Address("address"), T.CallValue("value"), T.MemOffset("inOffset"), T.Length("inSize"), T.MemOffset("retOffset"), T.Length("retSize") 94 | p.call(value=3, inOffset=4, inSize=5, retOffset=6,gas=1, address=2, retSize=7) 95 | 96 | assembled = p.assemble() 97 | self.assertEqual(assembled.as_hexstring, "60016101015b62c0fefe6007600660056004600360026001f1") 98 | 99 | self.assertEqual(assembled.disassemble().as_string.strip(), """PUSH1 01 100 | PUSH2 0101 101 | JUMPDEST 102 | PUSH3 c0fefe 103 | PUSH1 07 104 | PUSH1 06 105 | PUSH1 05 106 | PUSH1 04 107 | PUSH1 03 108 | PUSH1 02 109 | PUSH1 01 110 | CALL""".strip()) 111 | 112 | def test_instr_args_kwargs(self): 113 | p1 = EvmProgram() 114 | # T.Gas("gas"), T.Address("address"), T.CallValue("value"), T.MemOffset("inOffset"), T.Length("inSize"), T.MemOffset("retOffset"), T.Length("retSize") 115 | p1.call(value=3, inOffset=4, inSize=5, retOffset=6, gas=1, address=2, retSize=7) 116 | 117 | p2 = EvmProgram() 118 | #T.Gas("gas"), T.Address("address"), T.CallValue("value"), T.MemOffset("inOffset"), T.Length("inSize"), T.MemOffset("retOffset"), T.Length("retSize") 119 | p2.call(1,2,3,4,5,6,7) 120 | 121 | self.assertEqual(p1.assemble().as_hexstring, 122 | p2.assemble().as_hexstring) 123 | 124 | 125 | def test_partial_argumentlist_zerofill(self): 126 | p = EvmProgram().call(0x7bc9, 0x06, 0).gas() 127 | 128 | #print(p.assemble().disassemble().as_string) 129 | 130 | self.assertEqual(p.assemble().as_hexstring, "600060006000600060006006617bc9f15a") 131 | 132 | def test_mixed_arglist_zerofill(self): 133 | p = EvmProgram().call(0x7bc9, 0x06, inOffset=0xc0).gas() 134 | 135 | #print(p.assemble().disassemble().as_string) 136 | 137 | self.assertEqual(p.assemble().as_hexstring, "60006000600060c060006006617bc9f15a") 138 | 139 | def test_mixed_arglist_strict_mode(self): 140 | with self.assertRaises(Exception) as context: 141 | p = EvmProgram(strict=True).call(0x7bc9, 0x06, inOffset=0xc0).gas() 142 | #print(p.assemble().disassemble().as_string) 143 | self.assertEqual(p.assemble().as_hexstring, "60006000600060c060006006617bc9f15a") 144 | 145 | self.assertIn("strict mode".lower(), str(context.exception).lower()) 146 | -------------------------------------------------------------------------------- /tests/test_instruction.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Author : 4 | 5 | import unittest 6 | 7 | from evmdasm import registry, utils 8 | 9 | 10 | class InstructionTest(unittest.TestCase): 11 | 12 | def test_instruction_attrib_protection(self): 13 | jmp = registry.instruction.JUMP 14 | 15 | # check protection of attributes of Instruction 16 | # make sure these attribs raise exceptions 17 | for attrib in ("opcode", "name", "length_of_operand", "description", "args", "returns", "gas", "category", "pops", "pushes"): 18 | with self.assertRaises(AttributeError) as context: 19 | setattr(jmp, attrib, 1) 20 | 21 | # check unprotected attribs 22 | for attrib in ("next", "previous", "address"): 23 | setattr(jmp, attrib, 1) 24 | 25 | 26 | def test_instruction_set_operand(self): 27 | value = "c0fefe" 28 | push = registry.create_instruction("PUSH%d"%len(value)) 29 | push.operand = value 30 | 31 | self.assertEqual(push.operand, value) 32 | self.assertEqual(push.operand_bytes, utils.str_to_bytes(value)) 33 | self.assertEqual(push.operand_length, len(value)) 34 | 35 | def test_instruction_set_operand_bytes(self): 36 | value = b'\xc0\xfe\xfe' 37 | push = registry.create_instruction("PUSH%d" % len(value)) 38 | push.operand_bytes = value 39 | 40 | self.assertEqual(push.operand, utils.bytes_to_str(value, prefix="")) 41 | self.assertEqual(push.operand_bytes, value) 42 | self.assertEqual(push.operand_length, len(value)) 43 | 44 | -------------------------------------------------------------------------------- /tests/test_registry.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Author : 4 | 5 | import unittest 6 | 7 | from evmdasm import registry, instructions 8 | 9 | 10 | class RegistryTest(unittest.TestCase): 11 | 12 | def test_access_by_attribute_and_dict(self): 13 | self.assertEqual(registry.instruction.JUMP, registry.INSTRUCTIONS_BY_NAME["JUMP"]) 14 | 15 | def test_categories_set(self): 16 | for i in registry.INSTRUCTIONS: 17 | self.assertTrue(i.category) 18 | 19 | def test_create_instruction(self): 20 | self.assertEqual(registry.instruction.JUMP, registry.INSTRUCTIONS_BY_NAME["JUMP"]) 21 | self.assertNotEqual(registry.instruction.JUMP, registry.create_instruction(name="JUMP")) 22 | 23 | def test_interface(self): 24 | for name in registry.INSTRUCTION_MARKS_BASICBLOCK_END: 25 | self.assertIsInstance(name, str) 26 | 27 | self.assertTrue(registry.INSTRUCTIONS) 28 | for instr in registry.INSTRUCTIONS: 29 | self.assertIsInstance(instr, instructions.Instruction) 30 | 31 | self.assertTrue(registry.INSTRUCTIONS_BY_OPCODE) 32 | for instr in registry.INSTRUCTIONS_BY_OPCODE.values(): 33 | self.assertIsInstance(instr, instructions.Instruction) 34 | 35 | self.assertTrue(registry.INSTRUCTIONS_BY_NAME) 36 | for instr in registry.INSTRUCTIONS_BY_NAME.values(): 37 | self.assertIsInstance(instr, instructions.Instruction) 38 | 39 | self.assertTrue(registry.INSTRUCTIONS_BY_CATEGORY) 40 | for instrs in registry.INSTRUCTIONS_BY_CATEGORY.values(): 41 | for inst in instrs: 42 | self.assertIsInstance(instr, instructions.Instruction) 43 | 44 | # test one instruction 45 | self.assertIsInstance(registry.instruction.JUMP, instructions.Instruction) 46 | 47 | self.assertTrue(registry.INSTRUCTION_MARKS_BASICBLOCK_END) 48 | for name in registry.INSTRUCTION_MARKS_BASICBLOCK_END: 49 | self.assertIsInstance(name, str) 50 | 51 | 52 | class MyInstruction(instructions.Instruction): 53 | 54 | def __init__(self, opcode, name, length_of_operand=0, description=None, args=None, returns=None, gas=-1, 55 | category=None, pops=None, pushes=None, fork=None): 56 | super().__init__(opcode=opcode, name=name, 57 | length_of_operand=length_of_operand, 58 | description=description, 59 | args=args, returns=returns, 60 | gas=gas, category=category, 61 | pops=pops, pushes=pushes, fork=fork) 62 | 63 | # additional attribs 64 | self.annotations = [] 65 | self.xrefs = set([]) 66 | self.jumpto = None 67 | self.basicblock = None 68 | 69 | 70 | class InstructionRegistryTest(unittest.TestCase): 71 | 72 | def setUp(self): 73 | self.registry = registry.InstructionRegistry(instructions=registry.INSTRUCTIONS, _template_cls=MyInstruction) 74 | 75 | def test_custom_template(self): 76 | self.assertTrue(self.registry.instructions) 77 | for instr in self.registry.instructions: 78 | self.assertIsInstance(instr, MyInstruction) 79 | self.assertTrue(hasattr(instr, "xrefs")) 80 | 81 | self.assertTrue(self.registry.by_opcode) 82 | for instr in self.registry.by_opcode.values(): 83 | self.assertIsInstance(instr, MyInstruction) 84 | 85 | self.assertTrue(self.registry.by_name) 86 | for instr in self.registry.by_name.values(): 87 | self.assertIsInstance(instr, MyInstruction) 88 | 89 | self.assertTrue(self.registry.by_category) 90 | for instrs in self.registry.by_category.values(): 91 | for inst in instrs: 92 | self.assertIsInstance(instr, MyInstruction) 93 | 94 | # test one instruction 95 | self.assertIsInstance(self.registry.instruction.JUMP, MyInstruction) 96 | 97 | self.assertTrue(self.registry.instruction_marks_basicblock_end) 98 | for name in self.registry.instruction_marks_basicblock_end: 99 | self.assertIsInstance(name, str) 100 | 101 | --------------------------------------------------------------------------------