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 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 log_XX(computation: ComputationAPI, topic_count: int) -> None: if topic_count < 0 or topic_count > 4: raise TypeError("Invalid log topic size. Must be 0, 1, 2, 3, or 4") mem_start_position, size = computation.stack_pop_ints(2) if not topic_count: topics: Tuple[int, ...] = () elif topic_count > 1: topics = computation.stack_pop_ints(topic_count) else: topics = (computation.stack_pop1_int(), ) data_gas_cost = constants.GAS_LOGDATA * size topic_gas_cost = constants.GAS_LOGTOPIC * topic_count total_gas_cost = data_gas_cost + topic_gas_cost computation.consume_gas( total_gas_cost, reason="Log topic and data gas cost", ) computation.extend_memory(mem_start_position, size) log_data = computation.memory_read_bytes(mem_start_position, size) computation.add_log_entry( account=computation.msg.storage_address, topics=topics, data=log_data, )
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 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 identity(computation: ComputationAPI) -> ComputationAPI: word_count = ceil32(len(computation.msg.data)) // 32 gas_fee = constants.GAS_IDENTITY + word_count * constants.GAS_IDENTITYWORD computation.consume_gas(gas_fee, reason="Identity Precompile") computation.output = computation.msg.data_as_bytes return computation
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 sload(computation: ComputationAPI) -> None: slot = computation.stack_pop1_int() value = computation.state.get_storage( address=computation.msg.storage_address, slot=slot, ) computation.stack_push_int(value)
def sha256(computation: ComputationAPI) -> ComputationAPI: word_count = ceil32(len(computation.msg.data)) // 32 gas_fee = constants.GAS_SHA256 + word_count * constants.GAS_SHA256WORD computation.consume_gas(gas_fee, reason="SHA256 Precompile") input_bytes = computation.msg.data hash = hashlib.sha256(input_bytes).digest() computation.output = hash return computation
def not_op(computation: ComputationAPI) -> None: """ Not """ value = computation.stack_pop1_int() result = constants.UINT_256_MAX - value computation.stack_push_int(result)
def xor(computation: ComputationAPI) -> None: """ Bitwise XOr """ left, right = computation.stack_pop_ints(2) result = left ^ right computation.stack_push_int(result)
def or_op(computation: ComputationAPI) -> None: """ Bitwise Or """ left, right = computation.stack_pop_ints(2) result = left | right computation.stack_push_int(result)
def and_op(computation: ComputationAPI) -> None: """ Bitwise And """ left, right = computation.stack_pop_ints(2) result = left & right computation.stack_push_int(result)
def net_sstore(gas_schedule: NetSStoreGasSchedule, computation: ComputationAPI) -> None: slot, value = computation.stack_pop_ints(2) current_value = computation.state.get_storage( address=computation.msg.storage_address, slot=slot, ) original_value = computation.state.get_storage( address=computation.msg.storage_address, slot=slot, from_journal=False) gas_refund = 0 if current_value == value: gas_cost = gas_schedule.base else: if original_value == current_value: if original_value == 0: gas_cost = gas_schedule.create else: gas_cost = gas_schedule.update if value == 0: gas_refund += gas_schedule.remove_refund else: gas_cost = gas_schedule.base if original_value != 0: if current_value == 0: gas_refund -= gas_schedule.remove_refund if value == 0: gas_refund += gas_schedule.remove_refund if original_value == value: if original_value == 0: gas_refund += (gas_schedule.create - gas_schedule.base) else: gas_refund += (gas_schedule.update - gas_schedule.base) computation.consume_gas( gas_cost, reason= (f"SSTORE: {encode_hex(computation.msg.storage_address)}" f"[{slot}] -> {value} (current: {current_value} / original: {original_value})" )) if gas_refund: computation.refund_gas(gas_refund) computation.state.set_storage( address=computation.msg.storage_address, slot=slot, value=value, )
def ripemd160(computation: ComputationAPI) -> ComputationAPI: word_count = ceil32(len(computation.msg.data)) // 32 gas_fee = constants.GAS_RIPEMD160 + word_count * constants.GAS_RIPEMD160WORD computation.consume_gas(gas_fee, reason="RIPEMD160 Precompile") # TODO: this only works if openssl is installed. hash = hashlib.new('ripemd160', computation.msg.data).digest() padded_hash = pad32(hash) computation.output = padded_hash return computation
def calldataload(computation: ComputationAPI) -> None: """ Load call data into memory. """ start_position = computation.stack_pop1_int() value = computation.msg.data_as_bytes[start_position:start_position + 32] padded_value = value.ljust(32, b'\x00') normalized_value = padded_value.lstrip(b'\x00') computation.stack_push_bytes(normalized_value)
def eq(computation: ComputationAPI) -> None: """ Equality """ left, right = computation.stack_pop_ints(2) if left == right: result = 1 else: result = 0 computation.stack_push_int(result)
def gt(computation: ComputationAPI) -> None: """ Greater Comparison """ left, right = computation.stack_pop_ints(2) if left > right: result = 1 else: result = 0 computation.stack_push_int(result)
def byte_op(computation: ComputationAPI) -> None: """ Bitwise And """ position, value = computation.stack_pop_ints(2) if position >= 32: result = 0 else: result = (value // pow(256, 31 - position)) % 256 computation.stack_push_int(result)
def iszero(computation: ComputationAPI) -> None: """ Not """ value = computation.stack_pop1_int() if value == 0: result = 1 else: result = 0 computation.stack_push_int(result)
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 extcodehash(computation: ComputationAPI) -> None: """ Return the code hash for a given address. EIP: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1052.md """ account = force_bytes_to_address(computation.stack_pop1_bytes()) state = computation.state if state.account_is_empty(account): computation.stack_push_bytes(constants.NULL_BYTE) else: computation.stack_push_bytes(state.get_code_hash(account))
def blake2b_fcompress(computation: ComputationAPI) -> ComputationAPI: try: parameters = extract_blake2b_parameters(computation.msg.data_as_bytes) except ValidationError as exc: raise VMError(f"Blake2b input parameter validation failure: {exc}") from exc num_rounds = parameters[0] gas_cost = GAS_COST_PER_ROUND * num_rounds computation.consume_gas(gas_cost, reason=f"Blake2b Compress Precompile w/ {num_rounds} rounds") computation.output = blake2b.compress(*parameters) return computation
def push_XX(computation: ComputationAPI, size: int) -> None: raw_value = computation.code.read(size) # This is a performance-sensitive area. # Calling raw_value.ljust() when size == len(raw_value) is more expensive than # calling len(raw_value) and raw_len is typically the correct size already, # so this saves a bit of time: raw_len = len(raw_value) if raw_len == size: computation.stack_push_bytes(raw_value) else: padded_value = raw_value.ljust(size, b'\x00') computation.stack_push_bytes(padded_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 sgt(computation: ComputationAPI) -> None: """ Signed Greater Comparison """ left, right = map( unsigned_to_signed, computation.stack_pop_ints(2), ) if left > right: result = 1 else: result = 0 computation.stack_push_int(signed_to_unsigned(result))
def ecadd(computation: ComputationAPI, gas_cost: int = constants.GAS_ECADD) -> ComputationAPI: computation.consume_gas(gas_cost, reason='ECADD Precompile') try: result = _ecadd(computation.msg.data_as_bytes) except ValidationError: raise VMError("Invalid ECADD parameters") result_x, result_y = result result_bytes = b''.join(( pad32(int_to_big_endian(result_x.n)), pad32(int_to_big_endian(result_y.n)), )) computation.output = result_bytes return computation
def sstore(computation: ComputationAPI) -> None: slot, value = computation.stack_pop_ints(2) computation.state.set_storage( address=computation.msg.storage_address, slot=slot, value=value, )
def returndatacopy(computation: ComputationAPI) -> None: ( mem_start_position, returndata_start_position, size, ) = computation.stack_pop_ints(3) if returndata_start_position + size > len(computation.return_data): raise OutOfBoundsRead( "Return data length is not sufficient to satisfy request. Asked " f"for data from index {returndata_start_position} " f"to {returndata_start_position + size}. " f"Return data is {len(computation.return_data)} bytes in length.") 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="RETURNDATACOPY fee") value = computation.return_data[ returndata_start_position:returndata_start_position + size] computation.memory_write(mem_start_position, size, value)
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())