From 9d922fe9f40f89f00de672fa8cb0499bbddd9107 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Sun, 24 Oct 2021 16:15:17 +0100 Subject: [PATCH] Refactor opcode files and fix issues (#1532) --- mythril/analysis/module/modules/exceptions.py | 2 +- mythril/analysis/module/modules/suicide.py | 4 +- mythril/analysis/module/util.py | 4 +- mythril/analysis/security.py | 1 - mythril/disassembler/asm.py | 17 +- mythril/laser/ethereum/instruction_data.py | 186 +------------- mythril/laser/ethereum/instructions.py | 4 +- mythril/laser/ethereum/svm.py | 9 +- mythril/support/opcodes.py | 230 +++++++++++------- tests/instructions/static_call_test.py | 2 +- 10 files changed, 156 insertions(+), 303 deletions(-) diff --git a/mythril/analysis/module/modules/exceptions.py b/mythril/analysis/module/modules/exceptions.py index 1e5b1b52..698b603a 100644 --- a/mythril/analysis/module/modules/exceptions.py +++ b/mythril/analysis/module/modules/exceptions.py @@ -35,7 +35,7 @@ class Exceptions(DetectionModule): swc_id = ASSERT_VIOLATION description = "Checks whether any exception states are reachable." entry_point = EntryPoint.CALLBACK - pre_hooks = ["ASSERT_FAIL", "JUMP", "REVERT"] + pre_hooks = ["INVALID", "JUMP", "REVERT"] def _execute(self, state: GlobalState) -> None: """ diff --git a/mythril/analysis/module/modules/suicide.py b/mythril/analysis/module/modules/suicide.py index d7084fca..ee8d0bdf 100644 --- a/mythril/analysis/module/modules/suicide.py +++ b/mythril/analysis/module/modules/suicide.py @@ -28,7 +28,7 @@ class AccidentallyKillable(DetectionModule): swc_id = UNPROTECTED_SELFDESTRUCT description = DESCRIPTION entry_point = EntryPoint.CALLBACK - pre_hooks = ["SUICIDE"] + pre_hooks = ["SELFDESTRUCT"] def __init__(self): super().__init__() @@ -61,7 +61,7 @@ class AccidentallyKillable(DetectionModule): to = state.mstate.stack[-1] - log.debug("SUICIDE in function %s", state.environment.active_function_name) + log.debug("SELFDESTRUCT in function %s", state.environment.active_function_name) description_head = "Any sender can cause the contract to self-destruct." diff --git a/mythril/analysis/module/util.py b/mythril/analysis/module/util.py index a65f4631..cffae14a 100644 --- a/mythril/analysis/module/util.py +++ b/mythril/analysis/module/util.py @@ -2,12 +2,12 @@ from collections import defaultdict from typing import List, Optional, Callable, Mapping, Dict import logging -from mythril.support.opcodes import opcodes +from mythril.support.opcodes import OPCODES from mythril.analysis.module.base import DetectionModule, EntryPoint from mythril.analysis.module.loader import ModuleLoader log = logging.getLogger(__name__) -OP_CODE_LIST = [c[0] for _, c in opcodes.items()] +OP_CODE_LIST = OPCODES.keys() def get_detection_module_hooks( diff --git a/mythril/analysis/security.py b/mythril/analysis/security.py index 9d8fc4a7..2fd0a63e 100644 --- a/mythril/analysis/security.py +++ b/mythril/analysis/security.py @@ -1,7 +1,6 @@ """This module contains functionality for hooking in detection modules and executing them.""" -from mythril.support.opcodes import opcodes from mythril.analysis.module import ModuleLoader, reset_callback_modules from mythril.analysis.module.base import EntryPoint from mythril.analysis.report import Issue diff --git a/mythril/disassembler/asm.py b/mythril/disassembler/asm.py index 905b8d85..ea178fea 100644 --- a/mythril/disassembler/asm.py +++ b/mythril/disassembler/asm.py @@ -4,13 +4,10 @@ code disassembly.""" import re from collections import Generator -from mythril.support.opcodes import opcodes +from mythril.support.opcodes import OPCODES, ADDRESS, ADDRESS_OPCODE_MAPPING regex_PUSH = re.compile(r"^PUSH(\d*)$") -# Additional mnemonic to catch failed assertions -opcodes[254] = ("ASSERT_FAIL", 0, 0, 0) - class EvmInstruction: """Model to hold the information of the disassembly.""" @@ -54,9 +51,8 @@ def get_opcode_from_name(operation_name: str) -> int: :param operation_name: :return: """ - for op_code, value in opcodes.items(): - if operation_name == value[0]: - return op_code + if operation_name in OPCODES: + return OPCODES[operation_name][ADDRESS] raise RuntimeError("Unknown opcode") @@ -105,16 +101,15 @@ def disassemble(bytecode: bytes) -> list: while address < length: try: - op_code = opcodes[bytecode[address]] + op_code = ADDRESS_OPCODE_MAPPING[bytecode[address]] except KeyError: instruction_list.append(EvmInstruction(address, "INVALID")) address += 1 continue - op_code_name = op_code[0] - current_instruction = EvmInstruction(address, op_code_name) + current_instruction = EvmInstruction(address, op_code) - match = re.search(regex_PUSH, op_code_name) + match = re.search(regex_PUSH, op_code) if match: argument_bytes = bytecode[address + 1 : address + 1 + int(match.group(1))] current_instruction.argument = "0x" + argument_bytes.hex() diff --git a/mythril/laser/ethereum/instruction_data.py b/mythril/laser/ethereum/instruction_data.py index 0fb77602..f0bf71fb 100644 --- a/mythril/laser/ethereum/instruction_data.py +++ b/mythril/laser/ethereum/instruction_data.py @@ -1,191 +1,7 @@ from ethereum import opcodes from ethereum.utils import ceil32 from typing import Callable, Dict, Tuple, Union - - -Z_OPERATOR_TUPLE = (0, 1) -UN_OPERATOR_TUPLE = (1, 1) -BIN_OPERATOR_TUPLE = (2, 1) -T_OPERATOR_TUPLE = (3, 1) -GAS = "gas" -STACK = "stack" - -# Gas tuple contains (min_gas, max_gas) -# stack tuple contains (no_of_elements_popped, no_of_elements_pushed) - -OPCODES = { - "STOP": {GAS: (0, 0), STACK: (0, 0)}, - "ADD": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, - "MUL": {GAS: (5, 5), STACK: BIN_OPERATOR_TUPLE}, - "SUB": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, - "DIV": {GAS: (5, 5), STACK: BIN_OPERATOR_TUPLE}, - "SDIV": {GAS: (5, 5), STACK: BIN_OPERATOR_TUPLE}, - "MOD": {GAS: (5, 5), STACK: BIN_OPERATOR_TUPLE}, - "SMOD": {GAS: (5, 5), STACK: BIN_OPERATOR_TUPLE}, - "ADDMOD": {GAS: (8, 8), STACK: BIN_OPERATOR_TUPLE}, - "MULMOD": {GAS: (8, 8), STACK: T_OPERATOR_TUPLE}, - "EXP": {GAS: (10, 340), STACK: BIN_OPERATOR_TUPLE}, # exponent max 2^32 - "SIGNEXTEND": {GAS: (5, 5), STACK: BIN_OPERATOR_TUPLE}, - "LT": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, - "GT": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, - "SLT": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, - "SGT": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, - "EQ": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, - "AND": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, - "ISZERO": {GAS: (3, 3), STACK: UN_OPERATOR_TUPLE}, - "OR": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, - "XOR": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, - "NOT": {GAS: (3, 3), STACK: UN_OPERATOR_TUPLE}, - "BYTE": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, - "SHL": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, - "SHR": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, - "SAR": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, - "SHA3": { - GAS: ( - 30, - 30 + 6 * 8, - ), # max can be larger, but usually storage location with 8 words - STACK: BIN_OPERATOR_TUPLE, - }, - "ADDRESS": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, - "BALANCE": {GAS: (700, 700), STACK: UN_OPERATOR_TUPLE}, - "ORIGIN": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, - "CALLER": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, - "CALLVALUE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, - "CALLDATALOAD": {GAS: (3, 3), STACK: UN_OPERATOR_TUPLE}, - "CALLDATASIZE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, - "CALLDATACOPY": { - GAS: (2, 2 + 3 * 768), # https://ethereum.stackexchange.com/a/47556 - STACK: (3, 0), - }, - "CODESIZE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, - "CODECOPY": { - GAS: (2, 2 + 3 * 768), # https://ethereum.stackexchange.com/a/47556, - STACK: (3, 0), - }, - "GASPRICE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, - "EXTCODESIZE": {GAS: (700, 700), STACK: Z_OPERATOR_TUPLE}, - "EXTCODECOPY": { - GAS: (700, 700 + 3 * 768), # https://ethereum.stackexchange.com/a/47556 - STACK: (4, 0), - }, - "EXTCODEHASH": {GAS: (700, 700), STACK: UN_OPERATOR_TUPLE}, - "RETURNDATASIZE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, - "RETURNDATACOPY": {GAS: (3, 3), STACK: (3, 0)}, - "BLOCKHASH": {GAS: (20, 20), STACK: UN_OPERATOR_TUPLE}, - "COINBASE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, - "TIMESTAMP": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, - "NUMBER": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, - "DIFFICULTY": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, - "GASLIMIT": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, - "CHAINID": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, - "SELFBALANCE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, - "POP": {GAS: (2, 2), STACK: (1, 0)}, - # assume 1KB memory r/w cost as upper bound - "MLOAD": {GAS: (3, 96), STACK: UN_OPERATOR_TUPLE}, - "MSTORE": {GAS: (3, 98), STACK: (2, 0)}, - "MSTORE8": {GAS: (3, 98), STACK: (2, 0)}, - # assume 64 byte r/w cost as upper bound - "SLOAD": {GAS: (800, 800), STACK: UN_OPERATOR_TUPLE}, - "SSTORE": {GAS: (5000, 25000), STACK: (1, 0)}, - "JUMP": {GAS: (8, 8), STACK: (1, 0)}, - "JUMPI": {GAS: (10, 10), STACK: (2, 0)}, - "PC": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, - "MSIZE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, - "GAS": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, - "JUMPDEST": {GAS: (1, 1), STACK: (0, 0)}, - "PUSH1": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH2": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH3": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH4": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH5": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH6": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH7": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH8": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH9": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH10": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH11": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH12": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH13": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH14": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH15": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH16": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH17": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH18": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH19": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH20": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH21": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH22": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH23": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH24": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH25": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH26": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH27": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH28": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH29": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH30": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH31": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "PUSH32": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "DUP1": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "DUP2": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "DUP3": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "DUP4": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "DUP5": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "DUP6": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "DUP7": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "DUP8": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "DUP9": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "DUP10": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "DUP11": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "DUP12": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "DUP13": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "DUP14": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "DUP15": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "DUP16": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, - "SWAP1": {GAS: (3, 3), STACK: (0, 0)}, - "SWAP2": {GAS: (3, 3), STACK: (0, 0)}, - "SWAP3": {GAS: (3, 3), STACK: (0, 0)}, - "SWAP4": {GAS: (3, 3), STACK: (0, 0)}, - "SWAP5": {GAS: (3, 3), STACK: (0, 0)}, - "SWAP6": {GAS: (3, 3), STACK: (0, 0)}, - "SWAP7": {GAS: (3, 3), STACK: (0, 0)}, - "SWAP8": {GAS: (3, 3), STACK: (0, 0)}, - "SWAP9": {GAS: (3, 3), STACK: (0, 0)}, - "SWAP10": {GAS: (3, 3), STACK: (0, 0)}, - "SWAP11": {GAS: (3, 3), STACK: (0, 0)}, - "SWAP12": {GAS: (3, 3), STACK: (0, 0)}, - "SWAP13": {GAS: (3, 3), STACK: (0, 0)}, - "SWAP14": {GAS: (3, 3), STACK: (0, 0)}, - "SWAP15": {GAS: (3, 3), STACK: (0, 0)}, - "SWAP16": {GAS: (3, 3), STACK: (0, 0)}, - # apparently Solidity only allows byte32 as input to the log - # function. Virtually it could be as large as the block gas limit - # allows, but let's stick to the reasonable standard here. - # https://ethereum.stackexchange.com/a/1691 - "LOG0": {GAS: (375, 375 + 8 * 32), STACK: (2, 0)}, - "LOG1": {GAS: (2 * 375, 2 * 375 + 8 * 32), STACK: (3, 0)}, - "LOG2": {GAS: (3 * 375, 3 * 375 + 8 * 32), STACK: (4, 0)}, - "LOG3": {GAS: (4 * 375, 4 * 375 + 8 * 32), STACK: (5, 0)}, - "LOG4": {GAS: (5 * 375, 5 * 375 + 8 * 32), STACK: (6, 0)}, - "CREATE": {GAS: (32000, 32000), STACK: T_OPERATOR_TUPLE}, - "CREATE2": { - GAS: (32000, 32000), # TODO: Make the gas values dynamic - STACK: (4, 1), - }, - "CALL": {GAS: (700, 700 + 9000 + 25000), STACK: (7, 1)}, - "CALLCODE": {GAS: (700, 700 + 9000 + 25000), STACK: (7, 1)}, - "RETURN": {GAS: (0, 0), STACK: (2, 0)}, - "BASEFEE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, - "DELEGATECALL": {GAS: (700, 700 + 9000 + 25000), STACK: (6, 1)}, - "STATICCALL": {GAS: (700, 700 + 9000 + 25000), STACK: (6, 1)}, - "REVERT": {GAS: (0, 0), STACK: (2, 0)}, - "SUICIDE": {GAS: (5000, 30000), STACK: (1, 0)}, - "ASSERT_FAIL": {GAS: (0, 0), STACK: (0, 0)}, - "INVALID": {GAS: (0, 0), STACK: (0, 0)}, - "BEGINSUB": {GAS: (2, 2), STACK: (0, 0)}, - "JUMPSUB": {GAS: (10, 10), STACK: (1, 0)}, - "RETURNSUB": {GAS: (5, 5), STACK: (0, 0)}, -} # type: Dict[str, Dict[str, Tuple[int, int]]] +from mythril.support.opcodes import OPCODES, STACK, GAS def calculate_sha3_gas(length: int): diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index 0d4fbc24..c5715220 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -1850,14 +1850,14 @@ class Instruction: global_state.current_transaction.end(global_state, return_data) @StateTransition(is_state_mutation_instruction=True) - def suicide_(self, global_state: GlobalState): + def selfdestruct_(self, global_state: GlobalState): """ :param global_state: """ target = global_state.mstate.stack.pop() transfer_amount = global_state.environment.active_account.balance() - # Often the target of the suicide instruction will be symbolic + # Often the target of the selfdestruct instruction will be symbolic # If it isn't then we'll transfer the balance to the indicated contract global_state.world_state.balances[target] += transfer_amount diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py index c91826f5..2cb6c3fc 100644 --- a/mythril/laser/ethereum/svm.py +++ b/mythril/laser/ethereum/svm.py @@ -5,7 +5,7 @@ from copy import copy from datetime import datetime, timedelta from typing import Callable, Dict, DefaultDict, List, Tuple, Optional -from mythril.support.opcodes import opcodes as OPCODES +from mythril.support.opcodes import OPCODES from mythril.analysis.potential_issues import check_potential_issues from mythril.laser.execution_info import ExecutionInfo from mythril.laser.ethereum.cfg import NodeFlags, Node, Edge, JumpType @@ -110,7 +110,7 @@ class LaserEVM: self.iprof = iprof self.instr_pre_hook = {} # type: Dict[str, List[Callable]] self.instr_post_hook = {} # type: Dict[str, List[Callable]] - for op, _, _, _ in OPCODES.values(): + for op in OPCODES: self.instr_pre_hook[op] = [] self.instr_post_hook[op] = [] log.info("LASER EVM initialized with dynamic loader: " + str(dynamic_loader)) @@ -261,7 +261,6 @@ class LaserEVM: for state in new_states if state.world_state.constraints.is_possible ] - self.manage_cfg(op_code, new_states) # TODO: What about op_code is None? if new_states: self.work_list += new_states @@ -598,13 +597,13 @@ class LaserEVM: """Registers instructions hooks from plugins""" if hook_type == "pre": if opcode is None: - for op, _, _, _ in OPCODES.values(): + for op in OPCODES: self.instr_pre_hook[op].append(hook(op)) else: self.instr_pre_hook[opcode].append(hook) else: if opcode is None: - for op, _, _, _ in OPCODES.values(): + for op in OPCODES: self.instr_post_hook[op].append(hook(op)) else: self.instr_post_hook[opcode].append(hook) diff --git a/mythril/support/opcodes.py b/mythril/support/opcodes.py index 8cd86073..b863a72b 100644 --- a/mythril/support/opcodes.py +++ b/mythril/support/opcodes.py @@ -1,100 +1,144 @@ -# This pyethereum opcodes file with added opcodes -from typing import Dict, Tuple +from typing import Dict, Tuple, Union -opcodes = { - 0x00: ("STOP", 0, 0, 0), - 0x01: ("ADD", 2, 1, 3), - 0x02: ("MUL", 2, 1, 5), - 0x03: ("SUB", 2, 1, 3), - 0x04: ("DIV", 2, 1, 5), - 0x05: ("SDIV", 2, 1, 5), - 0x06: ("MOD", 2, 1, 5), - 0x07: ("SMOD", 2, 1, 5), - 0x08: ("ADDMOD", 3, 1, 8), - 0x09: ("MULMOD", 3, 1, 8), - 0x0A: ("EXP", 2, 1, 10), - 0x0B: ("SIGNEXTEND", 2, 1, 5), - 0x10: ("LT", 2, 1, 3), - 0x11: ("GT", 2, 1, 3), - 0x12: ("SLT", 2, 1, 3), - 0x13: ("SGT", 2, 1, 3), - 0x14: ("EQ", 2, 1, 3), - 0x15: ("ISZERO", 1, 1, 3), - 0x16: ("AND", 2, 1, 3), - 0x17: ("OR", 2, 1, 3), - 0x18: ("XOR", 2, 1, 3), - 0x19: ("NOT", 1, 1, 3), - 0x1A: ("BYTE", 2, 1, 3), - 0x1B: ("SHL", 2, 1, 3), - 0x1C: ("SHR", 2, 1, 3), - 0x1D: ("SAR", 2, 1, 3), - 0x20: ("SHA3", 2, 1, 30), - 0x30: ("ADDRESS", 0, 1, 2), - 0x31: ("BALANCE", 1, 1, 20), # now 400 - 0x32: ("ORIGIN", 0, 1, 2), - 0x33: ("CALLER", 0, 1, 2), - 0x34: ("CALLVALUE", 0, 1, 2), - 0x35: ("CALLDATALOAD", 1, 1, 3), - 0x36: ("CALLDATASIZE", 0, 1, 2), - 0x37: ("CALLDATACOPY", 3, 0, 3), - 0x38: ("CODESIZE", 0, 1, 2), - 0x39: ("CODECOPY", 3, 0, 3), - 0x3A: ("GASPRICE", 0, 1, 2), - 0x3B: ("EXTCODESIZE", 1, 1, 20), # now 700 - 0x3C: ("EXTCODECOPY", 4, 0, 20), # now 700 - 0x3D: ("RETURNDATASIZE", 0, 1, 2), - 0x3E: ("RETURNDATACOPY", 3, 0, 3), - 0x3F: ("EXTCODEHASH", 3, 0, 3), - 0x40: ("BLOCKHASH", 1, 1, 20), - 0x41: ("COINBASE", 0, 1, 2), - 0x42: ("TIMESTAMP", 0, 1, 2), - 0x43: ("NUMBER", 0, 1, 2), - 0x44: ("DIFFICULTY", 0, 1, 2), - 0x45: ("GASLIMIT", 0, 1, 2), - 0x46: ("CHAINID", 0, 1, 2), - 0x47: ("SELFBALANCE", 0, 1, 5), - 0x48: ("BASEFEE", 0, 1, 2), - 0x50: ("POP", 1, 0, 2), - 0x51: ("MLOAD", 1, 1, 3), - 0x52: ("MSTORE", 2, 0, 3), - 0x53: ("MSTORE8", 2, 0, 3), - 0x54: ("SLOAD", 1, 1, 50), # 200 now - 0x55: ("SSTORE", 2, 0, 0), - 0x56: ("JUMP", 1, 0, 8), - 0x57: ("JUMPI", 2, 0, 10), - 0x58: ("PC", 0, 1, 2), - 0x59: ("MSIZE", 0, 1, 2), - 0x5A: ("GAS", 0, 1, 2), - 0x5B: ("JUMPDEST", 0, 0, 1), - 0x5C: ("BEGINSUB", 0, 0, 2), - 0x5D: ("RETURNSUB", 0, 0, 5), - 0x5E: ("JUMPSUB", 1, 0, 10), - 0xA0: ("LOG0", 2, 0, 375), - 0xA1: ("LOG1", 3, 0, 750), - 0xA2: ("LOG2", 4, 0, 1125), - 0xA3: ("LOG3", 5, 0, 1500), - 0xA4: ("LOG4", 6, 0, 1875), - 0xF0: ("CREATE", 3, 1, 32000), - 0xF1: ("CALL", 7, 1, 40), # 700 now - 0xF2: ("CALLCODE", 7, 1, 40), # 700 now - 0xF3: ("RETURN", 2, 0, 0), - 0xF4: ("DELEGATECALL", 6, 1, 40), # 700 now - 0xF5: ("CREATE2", 3, 1, 32000), - 0xFA: ("STATICCALL", 6, 1, 40), - 0xFD: ("REVERT", 2, 0, 0), - 0xFF: ("SUICIDE", 1, 0, 0), # 5000 now -} # type: Dict[int, Tuple[str, int, int, int]] -opcodesMetropolis = {0x3D, 0x3E, 0xFA, 0xFD} +Z_OPERATOR_TUPLE = (0, 1) +UN_OPERATOR_TUPLE = (1, 1) +BIN_OPERATOR_TUPLE = (2, 1) +T_OPERATOR_TUPLE = (3, 1) +GAS = "gas" +STACK = "stack" +ADDRESS = "address" + +# Gas tuple contains (min_gas, max_gas) +# stack tuple contains (no_of_elements_popped, no_of_elements_pushed) + +# TODO: Make this more specific when TypedDict supports key re-usage. +OPCODES: Dict = { + "STOP": {GAS: (0, 0), STACK: (0, 0), ADDRESS: 0x00}, + "ADD": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE, ADDRESS: 0x01}, + "MUL": {GAS: (5, 5), STACK: BIN_OPERATOR_TUPLE, ADDRESS: 0x02}, + "SUB": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE, ADDRESS: 0x03}, + "DIV": {GAS: (5, 5), STACK: BIN_OPERATOR_TUPLE, ADDRESS: 0x04}, + "SDIV": {GAS: (5, 5), STACK: BIN_OPERATOR_TUPLE, ADDRESS: 0x05}, + "MOD": {GAS: (5, 5), STACK: BIN_OPERATOR_TUPLE, ADDRESS: 0x06}, + "SMOD": {GAS: (5, 5), STACK: BIN_OPERATOR_TUPLE, ADDRESS: 0x07}, + "ADDMOD": {GAS: (8, 8), STACK: BIN_OPERATOR_TUPLE, ADDRESS: 0x08}, + "MULMOD": {GAS: (8, 8), STACK: T_OPERATOR_TUPLE, ADDRESS: 0x09}, + "EXP": { + GAS: (10, 340), + STACK: BIN_OPERATOR_TUPLE, + ADDRESS: 0x0A, + }, # exponent max 2^32 + "SIGNEXTEND": {GAS: (5, 5), STACK: BIN_OPERATOR_TUPLE, ADDRESS: 0x0B}, + "LT": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE, ADDRESS: 0x10}, + "GT": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE, ADDRESS: 0x11}, + "SLT": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE, ADDRESS: 0x12}, + "SGT": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE, ADDRESS: 0x13}, + "EQ": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE, ADDRESS: 0x14}, + "ISZERO": {GAS: (3, 3), STACK: UN_OPERATOR_TUPLE, ADDRESS: 0x15}, + "AND": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE, ADDRESS: 0x16}, + "OR": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE, ADDRESS: 0x17}, + "XOR": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE, ADDRESS: 0x18}, + "NOT": {GAS: (3, 3), STACK: UN_OPERATOR_TUPLE, ADDRESS: 0x19}, + "BYTE": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE, ADDRESS: 0x1A}, + "SHL": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE, ADDRESS: 0x1B}, + "SHR": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE, ADDRESS: 0x1C}, + "SAR": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE, ADDRESS: 0x1D}, + "SHA3": { + GAS: ( + 30, + 30 + 6 * 8, + ), # max can be larger, but usually storage location with 8 words + STACK: BIN_OPERATOR_TUPLE, + ADDRESS: 0x20, + }, + "ADDRESS": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE, ADDRESS: 0x30}, + "BALANCE": {GAS: (700, 700), STACK: UN_OPERATOR_TUPLE, ADDRESS: 0x31}, + "ORIGIN": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE, ADDRESS: 0x32}, + "CALLER": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE, ADDRESS: 0x33}, + "CALLVALUE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE, ADDRESS: 0x34}, + "CALLDATALOAD": {GAS: (3, 3), STACK: UN_OPERATOR_TUPLE, ADDRESS: 0x35}, + "CALLDATASIZE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE, ADDRESS: 0x36}, + "CALLDATACOPY": { + GAS: (2, 2 + 3 * 768), # https://ethereum.stackexchange.com/a/47556 + STACK: (3, 0), + ADDRESS: 0x37, + }, + "CODESIZE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE, ADDRESS: 0x38}, + "CODECOPY": { + GAS: (2, 2 + 3 * 768), # https://ethereum.stackexchange.com/a/47556, + STACK: (3, 0), + ADDRESS: 0x39, + }, + "GASPRICE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE, ADDRESS: 0x3A}, + "EXTCODESIZE": {GAS: (700, 700), STACK: Z_OPERATOR_TUPLE, ADDRESS: 0x3B}, + "EXTCODECOPY": { + GAS: (700, 700 + 3 * 768), # https://ethereum.stackexchange.com/a/47556 + STACK: (4, 0), + ADDRESS: 0x3C, + }, + "EXTCODEHASH": {GAS: (700, 700), STACK: UN_OPERATOR_TUPLE, ADDRESS: 0x3F}, + "RETURNDATASIZE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE, ADDRESS: 0x3D}, + "RETURNDATACOPY": {GAS: (3, 3), STACK: (3, 0), ADDRESS: 0x3E}, + "BLOCKHASH": {GAS: (20, 20), STACK: UN_OPERATOR_TUPLE, ADDRESS: 0x40}, + "COINBASE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE, ADDRESS: 0x41}, + "TIMESTAMP": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE, ADDRESS: 0x42}, + "NUMBER": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE, ADDRESS: 0x43}, + "DIFFICULTY": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE, ADDRESS: 0x44}, + "GASLIMIT": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE, ADDRESS: 0x45}, + "CHAINID": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE, ADDRESS: 0x46}, + "SELFBALANCE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE, ADDRESS: 0x47}, + "BASEFEE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE, ADDRESS: 0x48}, + "POP": {GAS: (2, 2), STACK: (1, 0), ADDRESS: 0x50}, + # assume 1KB memory r/w cost as upper bound + "MLOAD": {GAS: (3, 96), STACK: UN_OPERATOR_TUPLE, ADDRESS: 0x51}, + "MSTORE": {GAS: (3, 98), STACK: (2, 0), ADDRESS: 0x52}, + "MSTORE8": {GAS: (3, 98), STACK: (2, 0), ADDRESS: 0x53}, + # assume 64 byte r/w cost as upper bound + "SLOAD": {GAS: (800, 800), STACK: UN_OPERATOR_TUPLE, ADDRESS: 0x54}, + "SSTORE": {GAS: (5000, 25000), STACK: (1, 0), ADDRESS: 0x55}, + "JUMP": {GAS: (8, 8), STACK: (1, 0), ADDRESS: 0x56}, + "JUMPI": {GAS: (10, 10), STACK: (2, 0), ADDRESS: 0x57}, + "PC": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE, ADDRESS: 0x58}, + "MSIZE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE, ADDRESS: 0x59}, + "GAS": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE, ADDRESS: 0x5A}, + "JUMPDEST": {GAS: (1, 1), STACK: (0, 0), ADDRESS: 0x5B}, + "BEGINSUB": {GAS: (2, 2), STACK: (0, 0), ADDRESS: 0x5C}, + "RETURNSUB": {GAS: (5, 5), STACK: (0, 0), ADDRESS: 0x5D}, + "JUMPSUB": {GAS: (10, 10), STACK: (1, 0), ADDRESS: 0x5E}, + # apparently Solidity only allows byte32 as input to the log + # function. Virtually it could be as large as the block gas limit + # allows, but let's stick to the reasonable standard here. + # https://ethereum.stackexchange.com/a/1691 + "LOG0": {GAS: (375, 375 + 8 * 32), STACK: (2, 0), ADDRESS: 0xA0}, + "LOG1": {GAS: (2 * 375, 2 * 375 + 8 * 32), STACK: (3, 0), ADDRESS: 0xA1}, + "LOG2": {GAS: (3 * 375, 3 * 375 + 8 * 32), STACK: (4, 0), ADDRESS: 0xA2}, + "LOG3": {GAS: (4 * 375, 4 * 375 + 8 * 32), STACK: (5, 0), ADDRESS: 0xA3}, + "LOG4": {GAS: (5 * 375, 5 * 375 + 8 * 32), STACK: (6, 0), ADDRESS: 0xA4}, + "CREATE": {GAS: (32000, 32000), STACK: T_OPERATOR_TUPLE, ADDRESS: 0xF0}, + "CREATE2": { + GAS: (32000, 32000), # TODO: Make the gas values dynamic + STACK: (4, 1), + ADDRESS: 0xF5, + }, + "CALL": {GAS: (700, 700 + 9000 + 25000), STACK: (7, 1), ADDRESS: 0xF1}, + "CALLCODE": {GAS: (700, 700 + 9000 + 25000), STACK: (7, 1), ADDRESS: 0xF2}, + "RETURN": {GAS: (0, 0), STACK: (2, 0), ADDRESS: 0xF3}, + "DELEGATECALL": {GAS: (700, 700 + 9000 + 25000), STACK: (6, 1), ADDRESS: 0xF4}, + "STATICCALL": {GAS: (700, 700 + 9000 + 25000), STACK: (6, 1), ADDRESS: 0xFA}, + "REVERT": {GAS: (0, 0), STACK: (2, 0), ADDRESS: 0xFD}, + "SELFDESTRUCT": {GAS: (5000, 30000), STACK: (1, 0), ADDRESS: 0xFF}, + "INVALID": {GAS: (0, 0), STACK: (0, 0), ADDRESS: 0xFE}, +} for i in range(1, 33): - opcodes[0x5F + i] = ("PUSH" + str(i), 0, 1, 3) + OPCODES[f"PUSH{i}"] = {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE, ADDRESS: 0x5F + i} for i in range(1, 17): - opcodes[0x7F + i] = ("DUP" + str(i), i, i + 1, 3) - opcodes[0x8F + i] = ("SWAP" + str(i), i + 1, i + 1, 3) + OPCODES[f"DUP{i}"] = {GAS: (3, 3), STACK: (0, 0), ADDRESS: 0x7F + i} + OPCODES[f"SWAP{i}"] = {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE, ADDRESS: 0x8F + i} + +ADDRESS_OPCODE_MAPPING = {} -reverse_opcodes = {} -for o in opcodes: - vars()[opcodes[o][0]] = opcodes[o] - reverse_opcodes[opcodes[o][0]] = o +for opcode, opcode_data in OPCODES.items(): + ADDRESS_OPCODE_MAPPING[opcode_data[ADDRESS]] = opcode diff --git a/tests/instructions/static_call_test.py b/tests/instructions/static_call_test.py index d0611ff0..e35abdc3 100644 --- a/tests/instructions/static_call_test.py +++ b/tests/instructions/static_call_test.py @@ -56,7 +56,7 @@ def test_staticcall(f1): test_data = ( - "suicide", + "selfdestruct", "create", "create2", "log0",