def get_stack_data(self, computation: BaseComputation) -> CreateOpcodeStackData: endowment, memory_start, memory_length, salt = computation.stack_pop( num_items=4, type_hint=constants.UINT256, ) return CreateOpcodeStackData(endowment, memory_start, memory_length, salt)
def apply_create_message(self, computation: BaseComputation, child_msg: Message) -> 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 sstore(computation: BaseComputation) -> None: slot, value = computation.stack_pop_ints(2) current_value = computation.state.get_storage( address=computation.msg.storage_address, slot=slot, ) is_currently_empty = not bool(current_value) is_going_to_be_empty = not bool(value) if is_currently_empty: gas_refund = 0 elif is_going_to_be_empty: gas_refund = constants.REFUND_SCLEAR else: gas_refund = 0 if is_currently_empty and is_going_to_be_empty: gas_cost = constants.GAS_SRESET elif is_currently_empty: gas_cost = constants.GAS_SSET elif is_going_to_be_empty: gas_cost = constants.GAS_SRESET else: gas_cost = constants.GAS_SRESET computation.consume_gas(gas_cost, reason="SSTORE: {0}[{1}] -> {2} ({3})".format( encode_hex(computation.msg.storage_address), slot, value, current_value, )) if gas_refund: computation.refund_gas(gas_refund) computation.state.set_storage( address=computation.msg.storage_address, slot=slot, value=value, )
def modexp(computation: BaseComputation) -> BaseComputation: """ https://github.com/ethereum/EIPs/pull/198 """ data = computation.msg.data_as_bytes gas_fee = _compute_modexp_gas_fee(data) computation.consume_gas(gas_fee, reason='MODEXP Precompile') result = _modexp(data) _, _, modulus_length = _extract_lengths(data) # Modulo 0 is undefined, return zero # https://math.stackexchange.com/questions/516251/why-is-n-mod-0-undefined result_bytes = b'' if modulus_length == 0 else zpad_left( int_to_big_endian(result), to_size=modulus_length) computation.output = result_bytes return computation
def jump(computation: BaseComputation) -> None: jump_dest = computation.stack_pop(type_hint=constants.UINT256) computation.code.pc = jump_dest next_opcode = computation.code.peek() if next_opcode != JUMPDEST: raise InvalidJumpDestination("Invalid Jump Destination") if not computation.code.is_valid_opcode(jump_dest): raise InvalidInstruction("Jump resulted in invalid instruction")
def _selfdestruct(computation: BaseComputation, 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 jump(computation: BaseComputation) -> None: jump_dest = computation.stack_pop1_int() computation.code.program_counter = jump_dest next_opcode = computation.code.peek() if next_opcode != JUMPDEST: raise InvalidJumpDestination("Invalid Jump Destination") if not computation.code.is_valid_opcode(jump_dest): raise InvalidInstruction("Jump resulted in invalid instruction")
def mload(computation: BaseComputation) -> None: start_position = computation.stack_pop1_int() computation.extend_memory(start_position, 32) value = computation.memory_read_bytes(start_position, 32) computation.stack_push_bytes(value)
def sha3(computation: BaseComputation) -> None: start_position, size = computation.stack_pop_ints(2) computation.extend_memory(start_position, size) sha3_bytes = computation.memory_read_bytes(start_position, size) word_count = ceil32(len(sha3_bytes)) // 32 gas_cost = constants.GAS_SHA3WORD * word_count computation.consume_gas(gas_cost, reason="SHA3: word gas cost") result = keccak(sha3_bytes) computation.stack_push_bytes(result)
def mload(computation: BaseComputation) -> None: start_position = computation.stack_pop(type_hint=constants.UINT256) computation.extend_memory(start_position, 32) value = computation.memory_read(start_position, 32) computation.stack_push(value)
def ecpairing(computation: BaseComputation) -> BaseComputation: if len(computation.msg.data) % 192: # data length must be an exact multiple of 192 raise VMError("Invalid ECPAIRING parameters") num_points = len(computation.msg.data) // 192 gas_fee = constants.GAS_ECPAIRING_BASE + num_points * constants.GAS_ECPAIRING_PER_POINT computation.consume_gas(gas_fee, reason='ECPAIRING Precompile') try: result = _ecpairing(computation.msg.data) except ValidationError: raise VMError("Invalid ECPAIRING parameters") if result is True: computation.output = pad32(b'\x01') elif result is False: computation.output = pad32(b'\x00') else: raise Exception("Invariant: unreachable code path") return computation
def exp(computation: BaseComputation, gas_per_byte: int) -> None: """ Exponentiation """ base, exponent = computation.stack_pop_ints(2) bit_size = exponent.bit_length() byte_size = ceil8(bit_size) // 8 if exponent == 0: result = 1 elif base == 0: result = 0 else: result = pow(base, exponent, constants.UINT_256_CEILING) computation.consume_gas( gas_per_byte * byte_size, reason="EXP: exponent bytes", ) computation.stack_push_int(result)
def jumpi(computation: BaseComputation) -> None: jump_dest, check_value = computation.stack_pop_ints(2) if check_value: computation.code.pc = jump_dest next_opcode = computation.code.peek() if next_opcode != JUMPDEST: raise InvalidJumpDestination("Invalid Jump Destination") if not computation.code.is_valid_opcode(jump_dest): raise InvalidInstruction("Jump resulted in invalid instruction")
def ecrecover(computation: BaseComputation) -> BaseComputation: computation.consume_gas(constants.GAS_ECRECOVER, reason="ECRecover Precompile") data = computation.msg.data_as_bytes raw_message_hash = data[:32] message_hash = pad32r(raw_message_hash) v_bytes = pad32r(data[32:64]) v = big_endian_to_int(v_bytes) r_bytes = pad32r(data[64:96]) r = big_endian_to_int(r_bytes) s_bytes = pad32r(data[96:128]) s = big_endian_to_int(s_bytes) try: validate_lt_secpk1n(r, title="ECRecover: R") validate_lt_secpk1n(s, title="ECRecover: S") validate_lte(v, 28, title="ECRecover: V") validate_gte(v, 27, title="ECRecover: V") except ValidationError: return computation canonical_v = v - 27 try: signature = keys.Signature(vrs=(canonical_v, r, s)) public_key = signature.recover_public_key_from_msg_hash(message_hash) except BadSignature: return computation address = public_key.to_canonical_address() padded_address = pad32(address) computation.output = padded_address return computation
def get_call_params(self, computation: BaseComputation) -> CallParams: gas = computation.stack_pop1_int() to = 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) return ( gas, 0, # value to, None, # sender None, # code_address memory_input_start_position, memory_input_size, memory_output_start_position, memory_output_size, False, # should_transfer_value, True, # is_static )
def mstore(computation: BaseComputation) -> None: start_position = computation.stack_pop(type_hint=constants.UINT256) value = computation.stack_pop(type_hint=constants.BYTES) padded_value = value.rjust(32, b'\x00') normalized_value = padded_value[-32:] computation.extend_memory(start_position, 32) computation.memory_write(start_position, 32, normalized_value)
def mstore(computation: BaseComputation) -> None: start_position = computation.stack_pop1_int() value = computation.stack_pop1_bytes() padded_value = value.rjust(32, b'\x00') normalized_value = padded_value[-32:] computation.extend_memory(start_position, 32) computation.memory_write(start_position, 32, normalized_value)
def apply_create_message(self, computation: BaseComputation, child_msg: Message) -> 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 finalize_computation(self, transaction: BaseTransaction, computation: BaseComputation) -> BaseComputation: # 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.trace( 'TRANSACTION REFUND: %s -> %s', gas_refund_amount, encode_hex(computation.msg.sender), ) self.vm_state.account_db.delta_balance(computation.msg.sender, gas_refund_amount) # Miner Fees transaction_fee = \ (transaction.gas - gas_remaining - gas_refund) * transaction.gas_price self.vm_state.logger.trace( 'TRANSACTION FEE: %s -> %s', transaction_fee, encode_hex(self.vm_state.coinbase), ) self.vm_state.account_db.delta_balance(self.vm_state.coinbase, transaction_fee) # Process Self Destructs for account, beneficiary 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.trace('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.account_db.set_balance(account, 0) self.vm_state.account_db.delete_account(account) return computation
def calldatacopy(computation: BaseComputation) -> None: ( mem_start_position, calldata_start_position, size, ) = computation.stack_pop_ints(3) computation.extend_memory(mem_start_position, size) word_count = ceil32(size) // 32 copy_gas_cost = word_count * constants.GAS_COPY computation.consume_gas(copy_gas_cost, reason="CALLDATACOPY fee") value = computation.msg.data_as_bytes[ calldata_start_position:calldata_start_position + size] padded_value = value.ljust(size, b'\x00') computation.memory_write(mem_start_position, size, padded_value)
def extcodecopy(computation: BaseComputation) -> None: 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) word_count = ceil32(size) // 32 copy_gas_cost = constants.GAS_COPY * word_count computation.consume_gas( copy_gas_cost, reason='EXTCODECOPY: word gas cost', ) 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)
def codecopy(computation: BaseComputation) -> None: ( mem_start_position, code_start_position, size, ) = computation.stack_pop_ints(3) computation.extend_memory(mem_start_position, size) word_count = ceil32(size) // 32 copy_gas_cost = constants.GAS_COPY * word_count computation.consume_gas( copy_gas_cost, reason="CODECOPY: word gas cost", ) with computation.code.seek(code_start_position): code_bytes = computation.code.read(size) padded_code_bytes = code_bytes.ljust(size, b'\x00') computation.memory_write(mem_start_position, size, padded_code_bytes)
def __call__(self, computation: BaseComputation) -> 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.account_db.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 = "Insufficient Funds: have: {0} | need: {1}".format( sender_balance, 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(0) else: if code_address: code = computation.state.account_db.get_code(code_address) else: code = computation.state.account_db.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 child_msg = computation.prepare_child_message(**child_msg_kwargs) child_computation = computation.apply_child_computation(child_msg) if child_computation.is_error: computation.stack_push(0) else: computation.stack_push(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())
def gas(computation: BaseComputation) -> None: gas_remaining = computation.get_gas_remaining() computation.stack_push_int(gas_remaining)
def gaslimit(computation: BaseComputation) -> None: computation.stack_push_int(computation.state.gas_limit)
def program_counter(computation: BaseComputation) -> None: pc = max(computation.code.program_counter - 1, 0) computation.stack_push_int(pc)
def blockhash(computation: BaseComputation) -> None: block_number = computation.stack_pop1_int() block_hash = computation.state.get_ancestor_hash(block_number) computation.stack_push_bytes(block_hash)
def sub(computation: BaseComputation) -> None: left, right = computation.stack_pop_ints(2) result = (left - right) & constants.UINT_256_MAX computation.stack_push_int(result)
def dup_XX(computation: BaseComputation, position: int) -> None: """ Stack item duplication. """ computation.stack_dup(position)
def basefee(computation: BaseComputation) -> None: computation.stack_push_int(computation.state.base_fee)