def revert(computation: ComputationAPI) -> None: start_position, size = computation.stack_pop_ints(2) computation.extend_memory(start_position, size) computation.output = computation.memory_read_bytes(start_position, size) raise Revert(computation.output)
def return_op(computation: ComputationAPI) -> None: start_position, size = computation.stack_pop_ints(2) computation.extend_memory(start_position, size) computation.output = computation.memory_read_bytes(start_position, size) raise Halt('RETURN')
def make_frontier_receipt(base_header: BlockHeaderAPI, transaction: SignedTransactionAPI, computation: ComputationAPI) -> ReceiptAPI: # Reusable for other forks # This skips setting the state root (set to 0 instead). The logic for making a state root # lives in the FrontierVM, so that state merkelization at each receipt is skipped at Byzantium+. logs = [ Log(address, topics, data) for address, topics, data in computation.get_log_entries() ] gas_remaining = computation.get_gas_remaining() gas_refund = computation.get_gas_refund() tx_gas_used = (transaction.gas - gas_remaining) - min( gas_refund, (transaction.gas - gas_remaining) // 2, ) gas_used = base_header.gas_used + tx_gas_used receipt = Receipt( state_root=ZERO_HASH32, gas_used=gas_used, logs=logs, ) return receipt
def get_call_params(self, computation: ComputationAPI) -> CallParams: gas = computation.stack_pop1_int() to = force_bytes_to_address(computation.stack_pop1_bytes()) ( value, memory_input_start_position, memory_input_size, memory_output_start_position, memory_output_size, ) = computation.stack_pop_ints(5) return ( gas, value, to, None, # sender None, # code_address memory_input_start_position, memory_input_size, memory_output_start_position, memory_output_size, True, # should_transfer_value, computation.msg.is_static, )
def get_call_params(self, computation: ComputationAPI) -> CallParams: gas = computation.stack_pop1_int() code_address = force_bytes_to_address(computation.stack_pop1_bytes()) ( memory_input_start_position, memory_input_size, memory_output_start_position, memory_output_size, ) = computation.stack_pop_ints(4) to = computation.msg.storage_address sender = computation.msg.sender value = computation.msg.value return ( gas, value, to, sender, code_address, memory_input_start_position, memory_input_size, memory_output_start_position, memory_output_size, False, # should_transfer_value, computation.msg.is_static, )
def consume_extcodecopy_word_cost(computation: ComputationAPI, size: int) -> None: word_count = ceil32(size) // 32 copy_gas_cost = constants.GAS_COPY * word_count computation.consume_gas( copy_gas_cost, reason='EXTCODECOPY: word gas cost', )
def selfdestruct_eip150(computation: ComputationAPI) -> None: beneficiary = force_bytes_to_address(computation.stack_pop1_bytes()) if not computation.state.account_exists(beneficiary): computation.consume_gas( constants.GAS_SELFDESTRUCT_NEWACCOUNT, reason=mnemonics.SELFDESTRUCT, ) _selfdestruct(computation, beneficiary)
def wrapped_logic_fn(computation: ComputationAPI) -> Any: """ Wrapper functionf or the logic function which consumes the base opcode gas cost prior to execution. """ computation.consume_gas( gas_cost, mnemonic, ) return logic_fn(computation)
def sstore_eip2929_generic(gas_schedule: NetSStoreGasSchedule, computation: ComputationAPI) -> int: slot = sstore_eip2200_generic(gas_schedule, computation) if _mark_storage_warm(computation, slot): gas_cost = berlin_constants.COLD_SLOAD_COST computation.consume_gas( gas_cost, reason=f"Implicit SLOAD during {mnemonics.SSTORE}") return slot
def finalize_gas_used(cls, transaction: SignedTransactionAPI, computation: ComputationAPI) -> int: gas_remaining = computation.get_gas_remaining() consumed_gas = transaction.gas - gas_remaining gross_refund = computation.get_gas_refund() net_refund = cls.calculate_net_gas_refund(consumed_gas, gross_refund) return consumed_gas - net_refund
def selfdestruct_eip161(computation: ComputationAPI) -> None: beneficiary = force_bytes_to_address(computation.stack_pop1_bytes()) is_dead = (not computation.state.account_exists(beneficiary) or computation.state.account_is_empty(beneficiary)) if is_dead and computation.state.get_balance( computation.msg.storage_address): computation.consume_gas( constants.GAS_SELFDESTRUCT_NEWACCOUNT, reason=mnemonics.SELFDESTRUCT, ) _selfdestruct(computation, beneficiary)
def selfdestruct_eip2929(computation: ComputationAPI) -> None: beneficiary = force_bytes_to_address(computation.stack_pop1_bytes()) if _mark_address_warm(computation, beneficiary): gas_cost = berlin_constants.COLD_ACCOUNT_ACCESS_COST computation.consume_gas( gas_cost, reason=f"Implicit account load during {mnemonics.SELFDESTRUCT}", ) selfdestruct_eip161_on_address(computation, beneficiary)
def calculate_gas_refund(cls, computation: ComputationAPI, gas_used: int) -> int: # Self Destruct Refunds num_deletions = len(computation.get_accounts_for_deletion()) if num_deletions: computation.refund_gas(REFUND_SELFDESTRUCT * num_deletions) # Gas Refunds gas_refunded = computation.get_gas_refund() return min(gas_refunded, gas_used // MAX_REFUND_QUOTIENT)
def extcodehash_eip2929(computation: ComputationAPI) -> None: """ Return the code hash for a given address. EIP: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1052.md """ address = force_bytes_to_address(computation.stack_pop1_bytes()) state = computation.state _consume_gas_for_account_load(computation, address, mnemonics.EXTCODEHASH) if state.account_is_empty(address): computation.stack_push_bytes(constants.NULL_BYTE) else: computation.stack_push_bytes(state.get_code_hash(address))
def sload_eip2929(computation: ComputationAPI) -> None: slot = computation.stack_pop1_int() if _mark_storage_warm(computation, slot): gas_cost = berlin_constants.COLD_SLOAD_COST else: gas_cost = berlin_constants.WARM_STORAGE_READ_COST computation.consume_gas(gas_cost, reason=mnemonics.SLOAD) value = computation.state.get_storage( address=computation.msg.storage_address, slot=slot, ) computation.stack_push_int(value)
def compute_eip150_msg_gas(*, computation: ComputationAPI, gas: int, extra_gas: int, value: int, mnemonic: str, callstipend: int) -> Tuple[int, int]: if computation.get_gas_remaining() < extra_gas: # It feels wrong to raise an OutOfGas exception outside of GasMeter, # but I don't see an easy way around it. raise OutOfGas(f"Out of gas: Needed {extra_gas}" f" - Remaining {computation.get_gas_remaining()}" f" - Reason: {mnemonic}") gas = min( gas, max_child_gas_eip150(computation.get_gas_remaining() - extra_gas)) total_fee = gas + extra_gas child_msg_gas = gas + (callstipend if value else 0) return child_msg_gas, total_fee
def calculate_gas_refund(cls, computation: ComputationAPI, gas_used: int) -> int: # Self destruct refunds were added in Frontier # London removes them in EIP-3529 gas_refunded = computation.get_gas_refund() return min(gas_refunded, gas_used // EIP3529_MAX_REFUND_QUOTIENT)
def gas_used_by(self, computation: ComputationAPI) -> int: """ Return the gas used by the given computation. In Frontier, for example, this is sum of the intrinsic cost and the gas used during computation. """ return self.get_intrinsic_gas() + computation.get_gas_used()
def get_stack_data(self, computation: ComputationAPI) -> CreateOpcodeStackData: endowment, memory_start, memory_length, salt = computation.stack_pop_ints( 4) return CreateOpcodeStackData(endowment, memory_start, memory_length, salt)
def apply_create_message(self, computation: ComputationAPI, child_msg: MessageAPI) -> None: child_computation = computation.apply_child_computation(child_msg) if child_computation.is_error: computation.stack_push_int(0) else: computation.stack_push_bytes(child_msg.storage_address) computation.return_gas(child_computation.get_gas_remaining())
def finalize_computation(self, transaction: SignedTransactionAPI, computation: ComputationAPI) -> ComputationAPI: transaction_context = self.vm_state.get_transaction_context(transaction) gas_remaining = computation.get_gas_remaining() gas_used = transaction.gas - gas_remaining gas_refund = self.calculate_gas_refund(computation, gas_used) gas_refund_amount = (gas_refund + gas_remaining) * transaction_context.gas_price if gas_refund_amount: self.vm_state.logger.debug2( 'TRANSACTION REFUND: %s -> %s', gas_refund_amount, encode_hex(computation.msg.sender), ) self.vm_state.delta_balance(computation.msg.sender, gas_refund_amount) # Miner Fees gas_used = transaction.gas - gas_remaining - gas_refund transaction_fee = gas_used * self.vm_state.get_tip(transaction) self.vm_state.logger.debug2( 'TRANSACTION FEE: %s -> %s', transaction_fee, encode_hex(self.vm_state.coinbase), ) self.vm_state.delta_balance(self.vm_state.coinbase, transaction_fee) # Process Self Destructs for account, _ in computation.get_accounts_for_deletion(): # TODO: need to figure out how we prevent multiple selfdestructs from # the same account and if this is the right place to put this. self.vm_state.logger.debug2('DELETING ACCOUNT: %s', encode_hex(account)) # TODO: this balance setting is likely superflous and can be # removed since `delete_account` does this. self.vm_state.set_balance(account, 0) self.vm_state.delete_account(account) return computation
def _selfdestruct(computation: ComputationAPI, beneficiary: Address) -> None: local_balance = computation.state.get_balance(computation.msg.storage_address) beneficiary_balance = computation.state.get_balance(beneficiary) # 1st: Transfer to beneficiary computation.state.set_balance(beneficiary, local_balance + beneficiary_balance) # 2nd: Zero the balance of the address being deleted (must come after # sending to beneficiary in case the contract named itself as the # beneficiary. computation.state.set_balance(computation.msg.storage_address, 0) computation.logger.debug2( "SELFDESTRUCT: %s (%s) -> %s", encode_hex(computation.msg.storage_address), local_balance, encode_hex(beneficiary), ) # 3rd: Register the account to be deleted computation.register_account_for_deletion(beneficiary) raise Halt('SELFDESTRUCT')
def make_receipt(cls, base_header: BlockHeaderAPI, transaction: SignedTransactionAPI, computation: ComputationAPI, state: StateAPI) -> ReceiptAPI: gas_used = base_header.gas_used + cls.finalize_gas_used( transaction, computation) if computation.is_error: status_code = EIP658_TRANSACTION_STATUS_CODE_FAILURE else: status_code = EIP658_TRANSACTION_STATUS_CODE_SUCCESS return transaction.make_receipt(status_code, gas_used, computation.get_log_entries())
def make_frontier_receipt(computation: ComputationAPI, new_cumulative_gas_used: int) -> ReceiptAPI: # Reusable for other forks # This skips setting the state root (set to 0 instead). The logic for making a state root # lives in the FrontierVM, so that state merkelization at each receipt is skipped at Byzantium+. logs = [ Log(address, topics, data) for address, topics, data in computation.get_log_entries() ] receipt = Receipt( state_root=ZERO_HASH32, gas_used=new_cumulative_gas_used, logs=logs, ) return receipt
def finalize_computation(self, transaction: SignedTransactionAPI, computation: ComputationAPI) -> ComputationAPI: # Self Destruct Refunds num_deletions = len(computation.get_accounts_for_deletion()) if num_deletions: computation.refund_gas(REFUND_SELFDESTRUCT * num_deletions) # Gas Refunds gas_remaining = computation.get_gas_remaining() gas_refunded = computation.get_gas_refund() gas_used = transaction.gas - gas_remaining gas_refund = min(gas_refunded, gas_used // 2) gas_refund_amount = (gas_refund + gas_remaining) * transaction.gas_price if gas_refund_amount: self.vm_state.logger.debug2( 'TRANSACTION REFUND: %s -> %s', gas_refund_amount, encode_hex(computation.msg.sender), ) # self.vm_state.delta_balance(computation.msg.sender, gas_refund_amount) ### DAEJUN changed ### # Miner Fees # transaction_fee = \ # (transaction.gas - gas_remaining - gas_refund) * transaction.gas_price transaction_fee = 0 self.vm_state.logger.debug2( 'TRANSACTION FEE: %s -> %s', transaction_fee, encode_hex(self.vm_state.coinbase), ) self.vm_state.delta_balance(self.vm_state.coinbase, transaction_fee) # Process Self Destructs for account, _ in computation.get_accounts_for_deletion(): # TODO: need to figure out how we prevent multiple selfdestructs from # the same account and if this is the right place to put this. self.vm_state.logger.debug2('DELETING ACCOUNT: %s', encode_hex(account)) # TODO: this balance setting is likely superflous and can be # removed since `delete_account` does this. self.vm_state.set_balance(account, 0) self.vm_state.delete_account(account) return computation
def apply_create_message(self, computation: ComputationAPI, child_msg: MessageAPI) -> None: # We need to ensure that creation operates on empty storage **and** # that if the initialization code fails that we revert the account back # to its original state root. snapshot = computation.state.snapshot() computation.state.delete_storage(child_msg.storage_address) child_computation = computation.apply_child_computation(child_msg) if child_computation.is_error: computation.state.revert(snapshot) computation.stack_push_int(0) else: computation.state.commit(snapshot) computation.stack_push_bytes(child_msg.storage_address) computation.return_gas(child_computation.get_gas_remaining())
def extcodecopy_execute(computation: ComputationAPI) -> Tuple[Address, int]: """ Runs the logical component of extcodecopy, without charging gas. :return (target_address, copy_size): useful for the caller to determine gas costs """ account = force_bytes_to_address(computation.stack_pop1_bytes()) ( mem_start_position, code_start_position, size, ) = computation.stack_pop_ints(3) computation.extend_memory(mem_start_position, size) code = computation.state.get_code(account) code_bytes = code[code_start_position:code_start_position + size] padded_code_bytes = code_bytes.ljust(size, b'\x00') computation.memory_write(mem_start_position, size, padded_code_bytes) return account, size
def selfdestruct(computation: ComputationAPI) -> None: beneficiary = force_bytes_to_address(computation.stack_pop1_bytes()) _selfdestruct(computation, beneficiary)
def __call__(self, computation: ComputationAPI) -> None: stack_data = self.get_stack_data(computation) gas_cost = self.get_gas_cost(stack_data) computation.consume_gas(gas_cost, reason=self.mnemonic) computation.extend_memory(stack_data.memory_start, stack_data.memory_length) insufficient_funds = computation.state.get_balance( computation.msg.storage_address) < stack_data.endowment stack_too_deep = computation.msg.depth + 1 > constants.STACK_DEPTH_LIMIT if insufficient_funds or stack_too_deep: computation.stack_push_int(0) return call_data = computation.memory_read_bytes(stack_data.memory_start, stack_data.memory_length) create_msg_gas = self.max_child_gas_modifier( computation.get_gas_remaining()) computation.consume_gas(create_msg_gas, reason=self.mnemonic) contract_address = self.generate_contract_address( stack_data, call_data, computation) is_collision = computation.state.has_code_or_nonce(contract_address) if is_collision: self.logger.debug2( "Address collision while creating contract: %s", encode_hex(contract_address), ) computation.stack_push_int(0) return child_msg = computation.prepare_child_message( gas=create_msg_gas, to=constants.CREATE_CONTRACT_ADDRESS, value=stack_data.endowment, data=b'', code=call_data, create_address=contract_address, ) self.apply_create_message(computation, child_msg)
def __call__(self, computation: ComputationAPI) -> None: computation.consume_gas( self.gas_cost, reason=self.mnemonic, ) ( gas, value, to, sender, code_address, memory_input_start_position, memory_input_size, memory_output_start_position, memory_output_size, should_transfer_value, is_static, ) = self.get_call_params(computation) computation.extend_memory(memory_input_start_position, memory_input_size) computation.extend_memory(memory_output_start_position, memory_output_size) call_data = computation.memory_read(memory_input_start_position, memory_input_size) # # Message gas allocation and fees # child_msg_gas, child_msg_gas_fee = self.compute_msg_gas( computation, gas, to, value) computation.consume_gas(child_msg_gas_fee, reason=self.mnemonic) # Pre-call checks sender_balance = computation.state.get_balance( computation.msg.storage_address) insufficient_funds = should_transfer_value and sender_balance < value stack_too_deep = computation.msg.depth + 1 > constants.STACK_DEPTH_LIMIT if insufficient_funds or stack_too_deep: computation.return_data = b'' if insufficient_funds: err_message = f"Insufficient Funds: have: {sender_balance} | need: {value}" elif stack_too_deep: err_message = "Stack Limit Reached" else: raise Exception("Invariant: Unreachable code path") self.logger.debug2( "%s failure: %s", self.mnemonic, err_message, ) computation.return_gas(child_msg_gas) computation.stack_push_int(0) else: if code_address: code = computation.state.get_code(code_address) else: code = computation.state.get_code(to) child_msg_kwargs = { 'gas': child_msg_gas, 'value': value, 'to': to, 'data': call_data, 'code': code, 'code_address': code_address, 'should_transfer_value': should_transfer_value, 'is_static': is_static, } if sender is not None: child_msg_kwargs['sender'] = sender # TODO: after upgrade to py3.6, use a TypedDict and try again child_msg = computation.prepare_child_message( **child_msg_kwargs) # type: ignore child_computation = computation.apply_child_computation(child_msg) if child_computation.is_error: computation.stack_push_int(0) else: computation.stack_push_int(1) if not child_computation.should_erase_return_data: actual_output_size = min(memory_output_size, len(child_computation.output)) computation.memory_write( memory_output_start_position, actual_output_size, child_computation.output[:actual_output_size], ) if child_computation.should_return_gas: computation.return_gas(child_computation.get_gas_remaining())