Ejemplo n.º 1
0
    def build_computation(self, message, transaction):
        """Apply the message to the VM."""
        transaction_context = self.get_transaction_context(transaction)
        if message.is_create:
            is_collision = self.vm_state.account_db.account_has_code_or_nonce(
                message.storage_address
            )

            if is_collision:
                # The address of the newly created contract has *somehow* collided
                # with an existing contract address.
                computation = self.vm_state.get_computation(message, transaction_context)
                computation._error = ContractCreationCollision(
                    "Address collision while creating contract: {0}".format(
                        encode_hex(message.storage_address),
                    )
                )
                self.vm_state.logger.debug(
                    "Address collision while creating contract: %s",
                    encode_hex(message.storage_address),
                )
            else:
                computation = self.vm_state.get_computation(
                    message,
                    transaction_context,
                ).apply_create_message()
        else:
            computation = self.vm_state.get_computation(
                message,
                transaction_context).apply_message()

        return computation
Ejemplo n.º 2
0
    def apply_create_message(self):
        computation = self.apply_message()

        if computation.is_error:
            return computation
        else:
            contract_code = computation.output

            if contract_code:
                contract_code_gas_fee = len(contract_code) * constants.GAS_CODEDEPOSIT
                try:
                    computation.consume_gas(
                        contract_code_gas_fee,
                        reason="Write contract code for CREATE",
                    )
                except OutOfGas:
                    computation.output = b''
                else:
                    self.logger.debug(
                        "SETTING CODE: %s -> length: %s | hash: %s",
                        encode_hex(self.msg.storage_address),
                        len(contract_code),
                        encode_hex(keccak(contract_code))
                    )
                    self.state.account_db.set_code(self.msg.storage_address, contract_code)
            return computation
Ejemplo n.º 3
0
    def apply_create_message(self, validate=True):
        snapshot = self.state.snapshot()

        if self.transaction_context.is_receive and not self.transaction_context.is_refund:
            # EIP161 nonce incrementation
            self.state.account_db.increment_nonce(self.msg.storage_address)

        computation = self.apply_message(validate=validate)

        if computation.is_error:
            self.state.revert(snapshot)
            return computation
        else:
            contract_code = computation.output

            if contract_code and len(contract_code) >= EIP170_CODE_SIZE_LIMIT:
                computation._error = OutOfGas(
                    "Contract code size exceeds EIP170 limit of {0}.  Got code of "
                    "size: {1}".format(
                        EIP170_CODE_SIZE_LIMIT,
                        len(contract_code),
                    ))
                self.state.revert(snapshot)
            elif contract_code:
                contract_code_gas_cost = len(
                    contract_code) * constants.GAS_CODEDEPOSIT
                try:
                    computation.consume_gas(
                        contract_code_gas_cost,
                        reason="Write contract code for CREATE",
                    )
                except OutOfGas as err:
                    # Different from Frontier: reverts state on gas failure while
                    # writing contract code.
                    computation._error = err
                    self.logger.debug("NOT ENOUGH GAS TO WRITE CONTRACT CODE")
                    self.state.revert(snapshot)
                else:

                    if self.transaction_context.is_receive and not self.transaction_context.is_refund:
                        # we only set the code if it is a receive transaction
                        if self.logger:
                            self.logger.debug(
                                "SETTING CODE: %s -> length: %s | hash: %s",
                                encode_hex(self.msg.storage_address),
                                len(contract_code),
                                encode_hex(keccak(contract_code)))

                        self.state.account_db.set_code(
                            self.msg.storage_address, contract_code)

                    self.state.commit(snapshot)

            else:
                self.state.commit(snapshot)
            return computation
Ejemplo n.º 4
0
    def build_evm_message(self, transaction):

        transaction_context = self.get_transaction_context(transaction)
        gas_fee = transaction.gas * transaction_context.gas_price

        # Buy Gas
        self.vm_state.account_db.delta_balance(transaction.sender, -1 * gas_fee)

        # Increment Nonce
        self.vm_state.account_db.increment_nonce(transaction.sender)

        # Setup VM Message
        message_gas = transaction.gas - transaction.intrinsic_gas

        if transaction.to == constants.CREATE_CONTRACT_ADDRESS:
            contract_address = generate_contract_address(
                transaction.sender,
                self.vm_state.account_db.get_nonce(transaction.sender) - 1,
            )
            data = b''
            code = transaction.data
        else:
            contract_address = None
            data = transaction.data
            code = self.vm_state.account_db.get_code(transaction.to)

        self.vm_state.logger.debug(
            (
                "TRANSACTION: sender: %s | to: %s | value: %s | gas: %s | "
                "gas-price: %s | s: %s | r: %s | v: %s | data-hash: %s"
            ),
            encode_hex(transaction.sender),
            encode_hex(transaction.to),
            transaction.value,
            transaction.gas,
            transaction.gas_price,
            transaction.s,
            transaction.r,
            transaction.v,
            encode_hex(keccak(transaction.data)),
        )

        message = Message(
            gas=message_gas,
            to=transaction.to,
            sender=transaction.sender,
            value=transaction.value,
            data=data,
            code=code,
            create_address=contract_address,
        )
        return message
Ejemplo n.º 5
0
def check_pow(block_number: int, mining_hash: Hash32, mix_hash: Hash32,
              nonce: bytes, difficulty: int) -> None:
    validate_length(mix_hash, 32, title="Mix Hash")
    validate_length(mining_hash, 32, title="Mining Hash")
    validate_length(nonce, 8, title="POW Nonce")
    cache = get_cache(block_number)
    mining_output = hashimoto_light(block_number, cache, mining_hash,
                                    big_endian_to_int(nonce))
    if mining_output[b'mix digest'] != mix_hash:
        raise ValidationError("mix hash mismatch; {0} != {1}".format(
            encode_hex(mining_output[b'mix digest']), encode_hex(mix_hash)))
    result = big_endian_to_int(mining_output[b'result'])
    validate_lte(result, 2**256 // difficulty, title="POW Difficulty")
Ejemplo n.º 6
0
    def __enter__(self) -> 'BaseComputation':
        self.logger.debug(
            (
                "COMPUTATION STARTING: gas: %s | from: %s | to: %s | value: %s "
                "| depth %s | static: %s"
            ),
            self.msg.gas,
            encode_hex(self.msg.sender),
            encode_hex(self.msg.to),
            self.msg.value,
            self.msg.depth,
            "y" if self.msg.is_static else "n",
        )

        return self
Ejemplo n.º 7
0
    def _decode_header_to_dict(cls, encoded_header: bytes) -> Iterator[Tuple[str, Any]]:
        if len(encoded_header) != cls.smc_encoded_size:
            raise ValidationError(
                "Expected encoded header to be of size: {0}. Got size {1} instead.\n- {2}".format(
                    cls.smc_encoded_size,
                    len(encoded_header),
                    encode_hex(encoded_header),
                )
            )

        start_indices = accumulate(lambda i, field: i + field[2], cls.fields_with_sizes, 0)
        field_bounds = sliding_window(2, start_indices)
        for byte_range, field in zip(field_bounds, cls._meta.fields):
            start_index, end_index = byte_range
            field_name, field_type = field

            field_bytes = encoded_header[start_index:end_index]
            if field_type == rlp.sedes.big_endian_int:
                # remove the leading zeros, to avoid `not minimal length` error in deserialization
                formatted_field_bytes = field_bytes.lstrip(b'\x00')
            elif field_type == address:
                formatted_field_bytes = field_bytes[-20:]
            else:
                formatted_field_bytes = field_bytes
            yield field_name, field_type.deserialize(formatted_field_bytes)
Ejemplo n.º 8
0
    def finalize_computation(self, transaction, computation):
        # 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.debug(
                '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.debug(
            '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.debug('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
Ejemplo n.º 9
0
    def __exit__(self, exc_type: None, exc_value: None, traceback: None) -> None:
        if exc_value and isinstance(exc_value, VMError):
            self.logger.debug(
                (
                    "COMPUTATION ERROR: gas: %s | from: %s | to: %s | value: %s | "
                    "depth: %s | static: %s | error: %s"
                ),
                self.msg.gas,
                encode_hex(self.msg.sender),
                encode_hex(self.msg.to),
                self.msg.value,
                self.msg.depth,
                "y" if self.msg.is_static else "n",
                exc_value,
            )
            self._error = exc_value
            if self.should_burn_gas:
                self.consume_gas(
                    self._gas_meter.gas_remaining,
                    reason=" ".join((
                        "Zeroing gas due to VM Exception:",
                        str(exc_value),
                    )),
                )

            # suppress VM exceptions
            return True
        elif exc_type is None:
            self.logger.debug(
                (
                    "COMPUTATION SUCCESS: from: %s | to: %s | value: %s | "
                    "depth: %s | static: %s | gas-used: %s | gas-remaining: %s"
                ),
                encode_hex(self.msg.sender),
                encode_hex(self.msg.to),
                self.msg.value,
                self.msg.depth,
                "y" if self.msg.is_static else "n",
                self.msg.gas - self._gas_meter.gas_remaining,
                self._gas_meter.gas_remaining,
            )
Ejemplo n.º 10
0
    def apply_create_message(self):
        snapshot = self.state.snapshot()

        computation = self.apply_message()

        if computation.is_error:
            self.state.revert(snapshot)
            return computation
        else:
            contract_code = computation.output

            if contract_code:
                contract_code_gas_cost = len(
                    contract_code) * constants.GAS_CODEDEPOSIT
                try:
                    computation.consume_gas(
                        contract_code_gas_cost,
                        reason="Write contract code for CREATE",
                    )
                except OutOfGas as err:
                    # Different from Frontier: reverts state on gas failure while
                    # writing contract code.
                    computation._error = err
                    self.state.revert(snapshot)
                else:
                    if self.logger:
                        self.logger.debug(
                            "SETTING CODE: %s -> length: %s | hash: %s",
                            encode_hex(self.msg.storage_address),
                            len(contract_code),
                            encode_hex(keccak(contract_code)))

                    self.state.account_db.set_code(self.msg.storage_address,
                                                   contract_code)
                    self.state.commit(snapshot)
            else:
                self.state.commit(snapshot)
            return computation
Ejemplo n.º 11
0
    def apply_message(self):
        snapshot = self.state.snapshot()

        if self.msg.depth > constants.STACK_DEPTH_LIMIT:
            raise StackDepthLimit("Stack depth limit reached")

        if self.msg.should_transfer_value and self.msg.value:
            sender_balance = self.state.account_db.get_balance(self.msg.sender)

            if sender_balance < self.msg.value:
                raise InsufficientFunds(
                    "Insufficient funds: {0} < {1}".format(sender_balance, self.msg.value)
                )

            self.state.account_db.delta_balance(self.msg.sender, -1 * self.msg.value)
            self.state.account_db.delta_balance(self.msg.storage_address, self.msg.value)

            self.logger.debug(
                "TRANSFERRED: %s from %s -> %s",
                self.msg.value,
                encode_hex(self.msg.sender),
                encode_hex(self.msg.storage_address),
            )

        self.state.account_db.touch_account(self.msg.storage_address)

        computation = self.apply_computation(
            self.state,
            self.msg,
            self.transaction_context,
        )

        if computation.is_error:
            self.state.revert(snapshot)
        else:
            self.state.commit(snapshot)

        return computation
Ejemplo n.º 12
0
def _selfdestruct(computation, beneficiary):
    local_balance = computation.state.account_db.get_balance(
        computation.msg.storage_address)
    beneficiary_balance = computation.state.account_db.get_balance(beneficiary)

    # 1st: Transfer to beneficiary
    computation.state.account_db.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.account_db.set_balance(computation.msg.storage_address,
                                             0)

    computation.logger.debug(
        "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')
Ejemplo n.º 13
0
    def add_possible_refunds_to_currently_executing_transaction(
        self,
        send_transaction: BaseTransaction,
        computation: 'BaseComputation',
        receive_transaction: BaseReceiveTransaction = None,
        refund_transaction: BaseReceiveTransaction = None,
    ) -> Union[BaseTransaction, BaseReceiveTransaction]:
        '''
        Receive transactions that have computation will have to refund any leftover gas. This refund amount depends
        on the computation which is why it is processed here and added the receive tx.

        :param send_transaction:
        :param computation:
        :param receive_transaction:
        :param refund_transaction:
        :return:
        '''
        if computation.transaction_context.is_refund:
            # this kind of receive transaction will always have 0 remaining refund so it doesnt need to be modified
            return refund_transaction

        elif computation.transaction_context.is_receive:
            # this kind of receive transaction may include a nonzero gas refund. Must add it in now
            # It gets a refund if send has data and is not create. ie. there was a computation on receive
            if computation.msg.data != b'' and not computation.msg.is_create:
                gas_remaining = computation.get_gas_remaining()
                gas_refunded = computation.get_gas_refund()

                gas_used = send_transaction.gas - gas_remaining
                gas_refund = min(gas_refunded, gas_used // 2)
                gas_refund_amount = (
                    gas_refund + gas_remaining) * send_transaction.gas_price

                self.vm_state.logger.debug(
                    'SAVING REFUND TO RECEIVE TX: %s -> %s',
                    gas_refund_amount,
                    encode_hex(computation.msg.sender),
                )
                receive_transaction = receive_transaction.copy(
                    remaining_refund=gas_refund_amount)

            return receive_transaction
        else:
            #this is a send transaction. Refunds are only possible on receive tx. So send it back unmodified
            return send_transaction
Ejemplo n.º 14
0
def sstore(computation):
    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,
    )
Ejemplo n.º 15
0
    def finalize_computation(self, transaction, computation):
        computation = super().finalize_computation(transaction, computation)

        #
        # EIP161 state clearing
        #
        touched_accounts = collect_touched_accounts(computation)

        for account in touched_accounts:
            should_delete = (
                self.vm_state.account_db.account_exists(account)
                and self.vm_state.account_db.account_is_empty(account))
            if should_delete:
                self.vm_state.logger.debug(
                    "CLEARING EMPTY ACCOUNT: %s",
                    encode_hex(account),
                )
                self.vm_state.account_db.delete_account(account)

        return computation
Ejemplo n.º 16
0
        for test_group, tests in test_groups.items():
            for filler, filler_kwargs in tests:
                test_name = get_test_name(filler)
                filename = test_name + ".json"

                filler_src_path = os.path.join(filler_dir, test_group,
                                               filename)
                filler_path = os.path.join(FILLER_PARENT_DIR, filler_src_path)
                test_path = os.path.join(TEST_PARENT_DIR, test_dir, test_group,
                                         filename)

                for path in [filler_path, test_path]:
                    os.makedirs(os.path.dirname(path), exist_ok=True)

                formatted_filler = filler_formatter(filler)
                filler_string = json.dumps(formatted_filler,
                                           indent=4,
                                           sort_keys=True)
                with open(filler_path, "w") as filler_file:
                    filler_file.write(filler_string)

                filler_hash = keccak(filler_string.encode("ascii"))
                info = {
                    "source": filler_src_path,
                    "sourceHash": encode_hex(filler_hash),
                }

                test = fill_test(filler, info=info, **filler_kwargs or {})
                with open(test_path, "w") as test_file:
                    json.dump(test, test_file, indent=4, sort_keys=True)
Ejemplo n.º 17
0
def test_basic_hexadecimal_encoding(value, expected):
    actual = encode_hex(value)
    assert actual == expected
Ejemplo n.º 18
0
 def __repr__(self) -> str:
     return "<CollationHeader shard={} period={} hash={}>".format(
         self.shard_id,
         self.period,
         encode_hex(self.hash)[2:10],
     )
Ejemplo n.º 19
0
 def hex_hash(self):
     return encode_hex(self.hash)
Ejemplo n.º 20
0
 def __repr__(self) -> str:
     return '<BaseBlockHeader #{0} {1}>'.format(
         self.block_number,
         encode_hex(self.hash)[2:10],
     )
Ejemplo n.º 21
0
    def apply_message(self, validate=True):
        snapshot = self.state.snapshot()

        if self.msg.depth > constants.STACK_DEPTH_LIMIT:
            raise StackDepthLimit("Stack depth limit reached")

        if self.msg.should_transfer_value:
            if self.transaction_context.is_refund:
                #this is a refund receive transaction
                try:
                    self.state.account_db.delete_receivable_transaction(
                        self.msg.sender,
                        self.transaction_context.receive_tx_hash)
                except ReceivableTransactionNotFound as e:
                    if validate:
                        raise e

                self.state.account_db.delta_balance(self.msg.sender,
                                                    self.msg.refund_amount)
                self.logger.debug(
                    "REFUNDED: %s into %s",
                    self.msg.refund_amount,
                    encode_hex(self.msg.sender),
                )

            elif self.transaction_context.is_receive:
                #this is a receive transaction
                try:
                    self.state.account_db.delete_receivable_transaction(
                        self.msg.storage_address,
                        self.transaction_context.send_tx_hash,
                        is_contract_deploy=self.msg.is_create)
                except ReceivableTransactionNotFound as e:
                    if validate:
                        raise e

                if self.msg.value:
                    self.state.account_db.delta_balance(
                        self.msg.storage_address, self.msg.value)
                    self.logger.debug(
                        "RECEIVED: %s into %s",
                        self.msg.value,
                        encode_hex(self.msg.storage_address),
                    )
            elif self.msg.value:
                # this is a send transaction
                if validate:
                    sender_balance = self.state.account_db.get_balance(
                        self.msg.sender)

                    if sender_balance < self.msg.value:
                        raise InsufficientFunds(
                            "Insufficient funds: {0} < {1}".format(
                                sender_balance, self.msg.value))

                self.state.account_db.delta_balance(self.msg.sender,
                                                    -1 * self.msg.value)

                self.logger.debug(
                    "SENT: %s from %s to pending transactions",
                    self.msg.value,
                    encode_hex(self.msg.sender),
                )

        self.state.account_db.touch_account(self.msg.storage_address)

        if self.transaction_context.is_refund:
            # We never run computations on a refund
            self.state.commit(snapshot)
            computation = self

        elif self.transaction_context.is_receive:
            # this is when we run all computation normally
            computation = self.apply_computation(
                self.state,
                self.msg,
                self.transaction_context,
            )

            if computation.is_error:
                self.state.revert(snapshot)
            else:
                self.state.commit(snapshot)

        else:
            # this is a send transaction. We only run computation if is_create = True, and in that case we only run it to determine
            # the gas cost. So we create a snapshot to remove any changes other thank calculating gas cost.

            if self.msg.is_create:
                computation_snapshot = self.state.snapshot()

                computation = self.apply_computation(
                    self.state,
                    self.msg,
                    self.transaction_context,
                )

                if computation.is_error:
                    # This will revert the computation snapshot as well.
                    self.state.revert(snapshot)
                else:
                    # computation worked, but we don't want it yet until the receive transaction. So lets revert the computation
                    # but commit the transaction above.
                    if self.logger:
                        self.logger.debug(
                            "REVERTING COMPUTATION FOR CONTRACT DEPLOYMENT. WILL DEPLOY ON RECEIVE TX."
                        )
                    self.state.revert(computation_snapshot)
                    self.state.commit(snapshot)

            else:
                computation = self

        return computation
Ejemplo n.º 22
0
def test_round_trip_with_hex_string_start(value):
    intermediate_value = decode_hex(value)
    round_trip_value = encode_hex(intermediate_value)
    assert round_trip_value == value
Ejemplo n.º 23
0
    def build_evm_message(
            self,
            send_transaction: BaseTransaction,
            transaction_context: BaseTransactionContext,
            receive_transaction: BaseReceiveTransaction = None) -> Message:
        if transaction_context.is_refund == True:

            # Setup VM Message
            message_gas = 0

            refund_amount = receive_transaction.remaining_refund

            contract_address = None
            data = b''
            code = b''

            self.vm_state.logger.debug(
                ("REFUND TRANSACTION: sender: %s | refund amount: %s "),
                encode_hex(send_transaction.sender),
                refund_amount,
            )

        elif transaction_context.is_receive == True:
            # this is a receive transaction - now we get to execute any code or data
            # transaction_context = self.get_transaction_context(send_transaction)
            # gas_fee = transaction.transaction.gas * transaction_context.gas_price

            # TODO:
            # fail niceley here so we can put a failed tx. the failed tx can be seen in the receipt status_code
            # we will have to refund the sender the money if this is the case.
            # so the amount of gas the send tx paid is saved as transaction.transaction.gas
            # Setup VM Message
            # message_gas = transaction.transaction.gas - transaction.transaction.intrinsic_gas -1 * gas_fee
            # I tested this, if this tx uses more gas than what was charged to the send tx it will fail.

            # Setup VM Message
            message_gas = send_transaction.gas - send_transaction.intrinsic_gas

            refund_amount = 0

            if send_transaction.to == constants.CREATE_CONTRACT_ADDRESS:
                # the contract address was already chosen on the send transaction. It is now the caller chain address
                contract_address = transaction_context.caller_chain_address
                data = b''
                code = send_transaction.data
            else:
                contract_address = None
                data = send_transaction.data
                code = self.vm_state.account_db.get_code(send_transaction.to)

            self.vm_state.logger.debug(
                ("RECEIVE TRANSACTION: hash: %s | sender: %s | to: %s | value: %s | gas: %s | "
                 "gas-price: %s | s: %s | r: %s | v: %s | data-hash: %s"),
                encode_hex(send_transaction.hash),
                encode_hex(send_transaction.sender),
                encode_hex(send_transaction.to),
                send_transaction.value,
                send_transaction.gas,
                send_transaction.gas_price,
                send_transaction.s,
                send_transaction.r,
                send_transaction.v,
                encode_hex(keccak(data)),
            )

        else:
            # this is a send transaction

            #transaction_context = self.get_transaction_context(send_transaction, receive_transaction)
            gas_fee = send_transaction.gas * transaction_context.gas_price

            #this is the default gas fee for the send tx that needs to be subtracted on the receive of a smart contract
            # Buy Gas
            self.vm_state.account_db.delta_balance(send_transaction.sender,
                                                   -1 * gas_fee)

            # Increment Nonce
            self.vm_state.account_db.increment_nonce(send_transaction.sender)

            # Setup VM Message
            message_gas = send_transaction.gas - send_transaction.intrinsic_gas

            refund_amount = 0

            #when a contract is created with a send transaction, do no computation.
            #we have to put the computation back. because it needs to charge computation
            #gas on the send. We just have to make sure it doesnt execute the transaction...
            #TODO: make sure the computation is not executed
            #temporarily we will just do no computation. This means interactions with
            #smart contracts will cost no gas until we finish this.

            if send_transaction.to == constants.CREATE_CONTRACT_ADDRESS:
                contract_address = generate_contract_address(
                    send_transaction.sender,
                    self.vm_state.account_db.get_nonce(send_transaction.sender)
                    - 1,
                )
                data = b''
                code = send_transaction.data
            else:
                contract_address = None
                data = send_transaction.data
                code = self.vm_state.account_db.get_code(send_transaction.to)

            self.vm_state.logger.debug(
                ("SEND TRANSACTION: sender: %s | to: %s | value: %s | gas: %s | "
                 "gas-price: %s | s: %s | r: %s | v: %s | data-hash: %s"),
                encode_hex(send_transaction.sender),
                encode_hex(send_transaction.to),
                send_transaction.value,
                send_transaction.gas,
                send_transaction.gas_price,
                send_transaction.s,
                send_transaction.r,
                send_transaction.v,
                encode_hex(keccak(send_transaction.data)),
            )

        message = Message(
            gas=message_gas,
            to=send_transaction.to,
            sender=send_transaction.sender,
            value=send_transaction.value,
            data=data,
            code=code,
            create_address=contract_address,
            refund_amount=refund_amount,
        )
        return message
Ejemplo n.º 24
0
    def finalize_computation(
            self, send_transaction: BaseTransaction,
            computation: 'BaseComputation') -> 'BaseComputation':
        #we only have to do any of this if it is a send transaction

        # Self Destruct Refunds
        num_deletions = len(computation.get_accounts_for_deletion())
        if num_deletions:
            computation.refund_gas(REFUND_SELFDESTRUCT * num_deletions)

        if not computation.transaction_context.is_receive and not computation.transaction_context.is_refund:
            # this is a send transaction. This is the only kind that could potentially refund gas

            if computation.msg.is_create or computation.msg.data == b'':
                # We are deploying a smart contract, we pay all computation fees now and refund leftover gas
                # OR
                # This transaction has no computation. It is just a HLS transaction. Send the transaction and refund leftover gas

                gas_remaining = computation.get_gas_remaining()
                gas_refunded = computation.get_gas_refund()

                gas_used = send_transaction.gas - gas_remaining
                gas_refund = min(gas_refunded, gas_used // 2)
                gas_refund_amount = (
                    gas_refund + gas_remaining) * send_transaction.gas_price

                if gas_refund_amount:
                    self.vm_state.logger.debug(
                        'GAS REFUND: %s -> %s',
                        gas_refund_amount,
                        encode_hex(computation.msg.sender),
                    )

                    self.vm_state.account_db.delta_balance(
                        computation.msg.sender, gas_refund_amount)

                # In order to keep the state consistent with the block headers, we want the newly created smart contract chain
                # to have an empty state. The smart contract data will be stored when the recieve transaction is executed.
                # At that time, the smart contract state can also be saved in the block header to keep the local state and
                # block header state consistent at all times.

                # This is also in line with the policy to only run computation on recieve transactions.

        if computation.transaction_context.is_receive and not computation.transaction_context.is_refund:
            # This is a receive transaction. This is the only kind that can process computations.
            # So this is the only case where we need to clean up stuff
            # 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.debug('DELETING ACCOUNT: %s',
                                           encode_hex(account))

                self.vm_state.account_db.delete_account(account)

            #
            # EIP161 state clearing
            #
            touched_accounts = collect_touched_accounts(computation)
            for account in touched_accounts:
                should_delete = (
                    self.vm_state.account_db.account_exists(account)
                    and self.vm_state.account_db.account_is_empty(account))
                if should_delete:
                    self.vm_state.logger.debug(
                        "CLEARING EMPTY ACCOUNT: %s",
                        encode_hex(account),
                    )
                    self.vm_state.account_db.delete_account(account)

        return computation
Ejemplo n.º 25
0
    def __call__(self, computation):
        computation.consume_gas(self.gas_cost, reason=self.mnemonic)

        value, start_position, size = computation.stack_pop(
            num_items=3,
            type_hint=constants.UINT256,
        )

        computation.extend_memory(start_position, size)

        insufficient_funds = computation.state.account_db.get_balance(
            computation.msg.storage_address) < value
        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(start_position, size)

        create_msg_gas = self.max_child_gas_modifier(
            computation.get_gas_remaining())
        computation.consume_gas(create_msg_gas, reason="CREATE")

        creation_nonce = computation.state.account_db.get_nonce(
            computation.msg.storage_address)
        computation.state.account_db.increment_nonce(
            computation.msg.storage_address)

        contract_address = generate_contract_address(
            computation.msg.storage_address,
            creation_nonce,
        )

        is_collision = computation.state.account_db.account_has_code_or_nonce(
            contract_address)

        if is_collision:
            self.logger.debug(
                "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=value,
            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())