def apply_computation( cls, state: StateAPI, message: MessageAPI, transaction_context: TransactionContextAPI) -> ComputationAPI: with cls(state, message, transaction_context) as computation: # Early exit on pre-compiles precompile = computation.precompiles.get(message.code_address, NO_RESULT) if precompile is not NO_RESULT: precompile(computation) return computation show_debug2 = computation.logger.show_debug2 opcode_lookup = computation.opcodes for opcode in computation.code: try: opcode_fn = opcode_lookup[opcode] except KeyError: opcode_fn = InvalidOpcode(opcode) if show_debug2: computation.logger.debug2( "OPCODE: 0x%x (%s) | pc: %s", opcode, opcode_fn.mnemonic, max(0, computation.code.program_counter - 1), ) try: opcode_fn(computation=computation) except Halt: break return computation
def apply_computation(cls, state: BaseState, message: Message, transaction_context: BaseTransactionContext) -> 'BaseComputation': """ Perform the computation that would be triggered by the VM message. """ with cls(state, message, transaction_context) as computation: # Early exit on pre-compiles precompile = computation.precompiles.get(message.code_address, NO_RESULT) if precompile is not NO_RESULT: precompile(computation) return computation show_debug2 = computation.logger.show_debug2 opcode_lookup = computation.opcodes for opcode in computation.code: try: opcode_fn = opcode_lookup[opcode] except KeyError: opcode_fn = InvalidOpcode(opcode) if show_debug2: computation.logger.debug2( "OPCODE: 0x%x (%s) | pc: %s", opcode, opcode_fn.mnemonic, max(0, computation.code.pc - 1), ) try: opcode_fn(computation=computation) except Halt: break return computation
def init_debug_session_signal_cb(self, code: CodeStreamAPI, opcode_lookup: Dict[int, OpcodeAPI], message: MessageAPI): """ :param code: Must be a deepcopy() version of the real code object. :param opcode_lookup: :param message: :return: """ logger.info("Init_debug signal received") # self.history.init = (code, opcode_lookup, message) # self.change_chains: [ChangeChain] = [] self._clear_table_widget(TableWidgetEnum.OPCODES) self._clear_table_widget(TableWidgetEnum.STACK) self._clear_table_widget(TableWidgetEnum.MEMORY) # reset the program counter, it might not be 0 and we need the whole thing. code.pc = 0 # this variable will be used to skip rounds of the iteration. # e.g. we read in the opcode push1 -> we will skip the next round (of drawing mnemonic) # e.g. we read push10 -> we skip 10 rounds skip = 0 for opcode in code: c = self.ui.opcodes_table_widget.rowCount() self.ui.opcodes_table_widget.insertRow(c) try: opcode_fn = opcode_lookup[opcode] except KeyError: opcode_fn = InvalidOpcode(opcode) o = QTableWidgetItem() o.setText(hex2(opcode)) self.ui.opcodes_table_widget.setItem(c, 0, o) if skip > 0: skip = skip - 1 continue m = QTableWidgetItem() g = QTableWidgetItem() m.setText(opcode_fn.mnemonic) g.setText(str(opcode_fn.gas_cost)) g.setTextAlignment(130) self.ui.opcodes_table_widget.setItem(c, 1, m) self.ui.opcodes_table_widget.setItem(c, 2, g) if 0x60 <= opcode <= 0x7f: skip = opcode - 0x5f self.ui.origin_label.setText("origin: 0x" + MASTER_ADDRESS.hex()) self.ui.origin_label.setToolTip("origin: 0x" + MASTER_ADDRESS.hex()) self.ui.from_label.setText("from: 0x" + message.sender.hex()) self.ui.from_label.setToolTip("from: 0x" + message.sender.hex()) self.ui.to_label.setText("to: 0x" + message.to.hex()) self.ui.to_label.setToolTip("to: 0x" + message.to.hex()) self.ui.value_label.setText("value: " + str(message.value)) self.ui.value_label.setToolTip("value: " + str(message.value)) self.ui.gas_limit_label.setText("gas limit: " + str(message.gas)) self.ui.gas_limit_label.setToolTip("gas limit: " + str(message.gas)) if message.is_create: self.ui.data_label.setText("data: 0x" + message.code.hex()[:8] + "...") self.ui.data_label.setToolTip("<FONT COLOR=white>0x" + message.code.hex() + "</FONT>") else: self.ui.data_label.setText("data: 0x" + message.data.hex()[:8] + "...") self.ui.data_label.setToolTip("<FONT COLOR=white>0x" + message.data.hex() + "</FONT>") self.ui.call_depth_label.setText("call depth: " + str(message.depth)) self.ui.call_depth_label.setToolTip("call depth: " + str(message.depth)) self._refresh_storage(message.storage_address) # I think the processEvents function does also process signals in the background. This isn't really documented # anywhere besides at some places in the docs where signals are also called events. The reason why I am # thinking that is that when this line is reached, the pre_computation_cb callback is called immediately # afterwards, which should not be and is only the case if there already is a signal in the queue waiting to be # handled and the processEvents function handles all the signals before updating the gui (which # is what we actually want). # To circumvent this, we need to put a lock in the worker thread that locks immediately after # firing the init_debug signal and will only release after processEvents has been called. That way the worker # thread will not add another signal to the queue before the current events (namely updating the gui which # is happening in this function) are processed. qApp.processEvents() self.init_lock.release()
def get_opcode_fn(self, opcode: int) -> Opcode: try: return self.opcodes[opcode] except KeyError: return InvalidOpcode(opcode)
def apply_computation(cls, state: StateAPI, message: MessageAPI, transaction_context: TransactionContextAPI) -> ComputationAPI: """ Perform the computation that would be triggered by the VM message. """ cls.parse_kwargs() with cls(state, message, transaction_context) as computation: # Early exit on pre-compiles precompile = computation.precompiles.get(message.code_address, NO_RESULT) if precompile is not NO_RESULT: precompile(computation) return computation opcode_lookup = computation.opcodes if cls.debug_mode: cls.init_debug_session.emit( deepcopy(computation.code), computation.opcodes, message ) cls.init_lock.acquire(True) for opcode in computation.code: try: opcode_fn = opcode_lookup[opcode] except KeyError: opcode_fn = InvalidOpcode(opcode) try: if cls.abort: logger.warning("Abort has been received") raise Halt if cls.debug_mode == MODE_DEBUG: cls.step_semaphore.acquire(1) cls.before_computation(computation, opcode, opcode_fn) elif cls.debug_mode == MODE_DEBUG_AUTO: sleep(cls.step_duration) cls.before_computation(computation, opcode, opcode_fn) # Since we cannot really read out every storage position that has been filled, we need # to keep track of every storage slot that gets filled from the beginning of a contracts # creation. This has to be done regardless of whether the user has activated debug mode or not, # since else we might never get to see a storage variable ever again. slot = "" value = "" if opcode == SSTORE: # first param is key second param is value arr = get_stack_content(computation._stack.values, 2) slot = arr[0] value = arr[1] opcode_fn(computation=computation) if cls.returned: cls.init_debug_session.emit(deepcopy(computation.code), computation.opcodes, message) cls.init_lock.acquire(True) cls.returned = False if opcode == REVERT: logger.info("Revert has been processed") cls.abort = True raise Halt pass elif opcode == SSTORE: cls.set_storage.emit(message.storage_address, slot, value) if cls.debug_mode: cls.storage_lock.acquire(True) if cls.debug_mode == MODE_DEBUG: cls.step_semaphore.acquire(1) cls.after_computation(computation, cls.last_consumed_gas_amount) elif cls.debug_mode == MODE_DEBUG_AUTO: sleep(cls.step_duration) cls.after_computation(computation, cls.last_consumed_gas_amount) except Halt: # if current opcode is RETURN, opcode_fn() will throw exception before next line is reached # this is to ensure that the UI gets updated in the last iteration as well if cls.debug_mode and not cls.abort: cls.returned = True cls.after_computation(computation, cls.last_consumed_gas_amount) elif cls.abort: cls.abort_transaction.emit() break return computation
def fuzz_apply_computation(cls, state, message, transaction_context): cls = cls.__class__ with cls(state, message, transaction_context) as computation: # Early exit on pre-compiles from eth.vm.computation import NO_RESULT precompile = computation.precompiles.get(message.code_address, NO_RESULT) if precompile is not NO_RESULT: precompile(computation) return computation opcode_lookup = computation.opcodes computation.trace = list() previous_stack = [] previous_call_address = None memory = None for opcode in computation.code: try: opcode_fn = opcode_lookup[opcode] except KeyError: from eth.vm.logic.invalid import InvalidOpcode opcode_fn = InvalidOpcode(opcode) from eth.exceptions import Halt from copy import deepcopy previous_pc = computation.code.pc previous_gas = computation.get_gas_remaining() try: if opcode == 0x42: # TIMESTAMP fuzz_timestamp_opcode_fn(computation=computation) elif opcode == 0x43: # NUMBER fuzz_blocknumber_opcode_fn(computation=computation) elif opcode == 0x31: # BALANCE fuzz_balance_opcode_fn(computation=computation, opcode_fn=opcode_fn) elif opcode == 0xf1: # CALL previous_call_address = fuzz_call_opcode_fn( computation=computation, opcode_fn=opcode_fn) elif opcode == 0x3b: # EXTCODESIZE fuzz_extcodesize_opcode_fn(computation=computation, opcode_fn=opcode_fn) elif opcode == 0x3d: # RETURNDATASIZE fuzz_returndatasize_opcode_fn(previous_call_address, computation=computation, opcode_fn=opcode_fn) elif opcode == 0x20: # SHA3 start_position, size = computation.stack_pop_ints(2) memory = computation.memory_read_bytes( start_position, size) computation.stack_push_int(size) computation.stack_push_int(start_position) opcode_fn(computation=computation) else: opcode_fn(computation=computation) except Halt: break finally: computation.trace.append({ "pc": max(0, previous_pc - 1), "op": opcode_fn.mnemonic, "depth": computation.msg.depth + 1, "error": deepcopy(computation._error), "stack": previous_stack, "memory": memory, "gas": computation.get_gas_remaining(), "gas_used_by_opcode": previous_gas - computation.get_gas_remaining() }) previous_stack = list(computation._stack.values) return computation