def log_XX(computation: BaseComputation, 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 log_XX(computation: BaseComputation, 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( num_items=2, type_hint=constants.UINT256) if not topic_count: topics = [] # type: List[int] elif topic_count > 1: topics = computation.stack_pop(num_items=topic_count, type_hint=constants.UINT256) else: topics = [ computation.stack_pop(num_items=topic_count, type_hint=constants.UINT256) ] 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 extcodecopy(computation: BaseComputation) -> None: account = force_bytes_to_address( computation.stack_pop(type_hint=constants.BYTES)) ( mem_start_position, code_start_position, size, ) = computation.stack_pop(num_items=3, type_hint=constants.UINT256) 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.account_db.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 ecrecover(computation: BaseComputation) -> BaseComputation: computation.consume_gas(constants.GAS_ECRECOVER, reason="ECRecover Precompile") raw_message_hash = computation.msg.data[:32] message_hash = pad32r(raw_message_hash) v_bytes = pad32r(computation.msg.data[32:64]) v = big_endian_to_int(v_bytes) r_bytes = pad32r(computation.msg.data[64:96]) r = big_endian_to_int(r_bytes) s_bytes = pad32r(computation.msg.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 returndatacopy(computation: BaseComputation) -> None: ( mem_start_position, returndata_start_position, size, ) = computation.stack_pop(num_items=3, type_hint=constants.UINT256) if returndata_start_position + size > len(computation.return_data): raise OutOfBoundsRead( "Return data length is not sufficient to satisfy request. Asked " "for data from index {0} to {1}. Return data is {2} bytes in " "length.".format( returndata_start_position, returndata_start_position + size, len(computation.return_data), ) ) 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 ecpairing( computation: BaseComputation, gas_cost_base: int = constants.GAS_ECPAIRING_BASE, gas_cost_per_point: int = constants.GAS_ECPAIRING_PER_POINT) -> 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 = gas_cost_base + num_points * gas_cost_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 returndatacopy(computation: BaseComputation) -> 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 selfdestruct_eip150(computation: BaseComputation) -> 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 identity(computation: BaseComputation) -> BaseComputation: 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 net_sstore(gas_schedule: NetSStoreGasSchedule, computation: BaseComputation) -> 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="SSTORE: {}[{}] -> {} (current: {} / original: {})".format( encode_hex(computation.msg.storage_address), slot, value, current_value, original_value, )) if gas_refund: computation.refund_gas(gas_refund) computation.state.set_storage( address=computation.msg.storage_address, slot=slot, value=value, )
def sha256(computation: BaseComputation) -> BaseComputation: 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 sstore_eip1283(computation: BaseComputation) -> 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 = constants.GAS_SSTORE_EIP1283_NOOP else: if original_value == current_value: if original_value == 0: gas_cost = constants.GAS_SSTORE_EIP1283_INIT else: gas_cost = constants.GAS_SSTORE_EIP1283_CLEAN if value == 0: gas_refund += constants.GAS_SSTORE_EIP1283_CLEAR_REFUND else: gas_cost = constants.GAS_SSTORE_EIP1283_NOOP if original_value != 0: if current_value == 0: gas_refund -= constants.GAS_SSTORE_EIP1283_CLEAR_REFUND if value == 0: gas_refund += constants.GAS_SSTORE_EIP1283_CLEAR_REFUND if original_value == value: if original_value == 0: gas_refund += constants.GAS_SSTORE_EIP1283_RESET_CLEAR_REFUND else: gas_refund += constants.GAS_SSTORE_EIP1283_RESET_REFUND computation.consume_gas( gas_cost, reason="SSTORE: {0}[{1}] -> {2} (current: {3} / original: {4})".format( encode_hex(computation.msg.storage_address), slot, value, current_value, original_value, )) if gas_refund: computation.refund_gas(gas_refund) computation.state.set_storage( address=computation.msg.storage_address, slot=slot, value=value, )
def __call__(self, computation: BaseComputation) -> 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.account_db.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(0) return call_data = computation.memory_read(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.account_db.account_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(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, ) child_computation = computation.apply_child_computation(child_msg) if child_computation.is_error: computation.stack_push(0) else: computation.stack_push(contract_address) computation.return_gas(child_computation.get_gas_remaining())
def ripemd160(computation: BaseComputation) -> BaseComputation: 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 selfdestruct_eip161(computation: BaseComputation) -> None: beneficiary = force_bytes_to_address( computation.stack_pop(type_hint=constants.BYTES)) is_dead = (not computation.state.account_db.account_exists(beneficiary) or computation.state.account_db.account_is_empty(beneficiary)) if is_dead and computation.state.account_db.get_balance( computation.msg.storage_address): computation.consume_gas( constants.GAS_SELFDESTRUCT_NEWACCOUNT, reason=mnemonics.SELFDESTRUCT, ) _selfdestruct(computation, beneficiary)
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 blake2b_fcompress(computation: BaseComputation) -> BaseComputation: 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 = _do_compression(*parameters) return computation
def ecmul(computation: BaseComputation) -> BaseComputation: computation.consume_gas(constants.GAS_ECMUL, reason='ECMUL Precompile') try: result = _ecmull(computation.msg.data) except ValidationError: raise VMError("Invalid ECMUL 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 ecadd( computation: BaseComputation, gas_cost: int = constants.GAS_ECADD) -> BaseComputation: 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 modexp(computation: BaseComputation) -> BaseComputation: """ https://github.com/ethereum/EIPs/pull/198 """ gas_fee = _compute_modexp_gas_fee(computation.msg.data) computation.consume_gas(gas_fee, reason='MODEXP Precompile') result = _modexp(computation.msg.data) _, _, modulus_length = _extract_lengths(computation.msg.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 sstore(computation: BaseComputation) -> None: slot, value = computation.stack_pop(num_items=2, type_hint=constants.UINT256) current_value = computation.state.account_db.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.account_db.set_storage( address=computation.msg.storage_address, slot=slot, value=value, )
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 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=(f"SSTORE: {encode_hex(computation.msg.storage_address)}" f"[{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 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 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 net_sstore(gas_schedule: NetSStoreGasSchedule, computation: BaseComputation) -> int: """ :return slot: where the new value was stored """ 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.sload_gas else: if original_value == current_value: if original_value == 0: gas_cost = gas_schedule.sstore_set_gas else: gas_cost = gas_schedule.sstore_reset_gas if value == 0: gas_refund += gas_schedule.sstore_clears_schedule else: gas_cost = gas_schedule.sload_gas if original_value != 0: if current_value == 0: gas_refund -= gas_schedule.sstore_clears_schedule if value == 0: gas_refund += gas_schedule.sstore_clears_schedule if original_value == value: if original_value == 0: gas_refund += (gas_schedule.sstore_set_gas - gas_schedule.sload_gas) else: gas_refund += (gas_schedule.sstore_reset_gas - gas_schedule.sload_gas) 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, ) return slot