Esempio n. 1
0
def _call_and_transact(
    contract_function: ContractFunction, transaction_params: Optional[Dict] = None
) -> str:
    """ Executes contract_function.{call, transaction}(transaction_params) and returns txhash """
    # First 'call' might raise an exception
    contract_function.call(transaction_params)
    return contract_function.transact(transaction_params)
Esempio n. 2
0
def call_and_transact(
    contract_function: ContractFunction,
    transaction_params: Optional[TxParams] = None,
) -> HexBytes:
    """ Executes contract_function.{call, transaction}(transaction_params) and returns txhash """
    # First 'call' might raise an exception
    contract_function.call(transaction_params)
    return contract_function.transact(transaction_params)
Esempio n. 3
0
    def build_contract_transaction(
        self,
        contract_function: ContractFunction,
        sender_address: str,
        payload: dict = None,
        transaction_gas_limit: Optional[int] = None,
        gas_estimation_multiplier: Optional[float] = None,
        use_pending_nonce: Optional[bool] = None,
    ) -> dict:

        if transaction_gas_limit is not None:
            self.log.warn(
                "The transaction gas limit of {transaction_gas_limit} will override gas estimation attempts"
            )

        # Sanity checks for the gas estimation multiplier
        if gas_estimation_multiplier is not None:
            if not 1 <= gas_estimation_multiplier <= 3:  # Arbitrary upper bound.
                raise ValueError(
                    f"The gas estimation multiplier should be a float between 1 and 3, "
                    f"but we received {gas_estimation_multiplier}.")

        payload = self.build_payload(
            sender_address=sender_address,
            payload=payload,
            transaction_gas_limit=transaction_gas_limit,
            use_pending_nonce=use_pending_nonce)
        self.__log_transaction(transaction_dict=payload,
                               contract_function=contract_function)
        try:
            if 'gas' not in payload:  # i.e., transaction_gas_limit is not None
                # As web3 buildTransaction() will estimate gas with block identifier "pending" by default,
                # explicitly estimate gas here with block identifier 'latest' if not otherwise specified
                # as a pending transaction can cause gas estimation to fail, notably in case of worklock refunds.
                payload['gas'] = contract_function.estimateGas(
                    payload, block_identifier='latest')
            transaction_dict = contract_function.buildTransaction(payload)
        except (TestTransactionFailed, ValidationError, ValueError) as error:
            # Note: Geth (1.9.15) raises ValueError in the same condition that pyevm raises ValidationError here.
            # Treat this condition as "Transaction Failed" during gas estimation.
            raise self._handle_failed_transaction(
                exception=error,
                transaction_dict=payload,
                contract_function=contract_function,
                logger=self.log)

        # Increase the estimated gas limit according to the gas estimation multiplier, if any.
        if gas_estimation_multiplier and not transaction_gas_limit:
            gas_estimation = transaction_dict['gas']
            overestimation = int(
                math.ceil(gas_estimation * gas_estimation_multiplier))
            self.log.debug(
                f"Gas limit for this TX was increased from {gas_estimation} to {overestimation}, "
                f"using a multiplier of {gas_estimation_multiplier}.")
            transaction_dict['gas'] = overestimation
            # TODO: What if we're going over the block limit? Not likely, but perhaps worth checking (NRN)

        return transaction_dict
Esempio n. 4
0
def submit_update(network: str, web3_client: Web3,
                  function_call: ContractFunction) -> None:
    tx_params = get_transaction_params(network, web3_client)
    estimated_gas = function_call.estimateGas(tx_params)

    # add 10% margin to the estimated gas
    tx_params["gas"] = int(estimated_gas * 0.1) + estimated_gas

    # execute transaction
    tx_hash = function_call.transact(tx_params)
    logger.info(f"[{network}] Submitted transaction: {Web3.toHex(tx_hash)}")
    wait_for_transaction(network, web3_client, tx_hash)
 def execute_transaction(self, contract_function: ContractFunction,
                         **kwargs) -> str:
     gas_price: int = self.gas_price
     transaction_args: Dict[str, Any] = {
         "from": self.address,
         "nonce": self.nonce,
         "chainId": self.chain.value,
         "gasPrice": gas_price,
     }
     transaction_args.update(kwargs)
     transaction: Dict[str, Any] = contract_function.buildTransaction(
         transaction_args)
     if "gas" not in transaction:
         estimate_gas: int = 1000000
         try:
             estimate_gas = self._w3.eth.estimateGas(transaction)
         except ValueError:
             self.logger().error(
                 f"Failed to estimate gas. Using default of 1000000.")
         transaction["gas"] = estimate_gas
     signed_transaction: AttributeDict = self._account.signTransaction(
         transaction)
     tx_hash: str = signed_transaction.hash.hex()
     self.schedule_eth_transaction(signed_transaction, gas_price)
     return tx_hash
Esempio n. 6
0
    def build_contract_transaction(
        self,
        contract_function: ContractFunction,
        sender_address: str,
        payload: dict = None,
        transaction_gas_limit: int = None,
    ) -> dict:

        payload = self.build_payload(
            sender_address=sender_address,
            payload=payload,
            transaction_gas_limit=transaction_gas_limit)
        self.__log_transaction(transaction_dict=payload,
                               contract_function=contract_function)
        try:
            transaction_dict = contract_function.buildTransaction(
                payload)  # Gas estimation occurs here
        except (TestTransactionFailed, ValidationError, ValueError) as error:
            # Note: Geth raises ValueError in the same condition that pyevm raises ValidationError here.
            # Treat this condition as "Transaction Failed" during gas estimation.
            raise self._handle_failed_transaction(
                exception=error,
                transaction_dict=payload,
                contract_function=contract_function,
                logger=self.log)
        return transaction_dict
Esempio n. 7
0
    def execute_transaction(self, contract_function: ContractFunction, **kwargs) -> str:
        """
        This function WILL result in immediate network calls (e.g. to get the gas price, nonce and gas cost), even
        though it is written in sync manner.

        :param contract_function:
        :param kwargs:
        :return:
        """
        if self._network_status is not NetworkStatus.CONNECTED:
            raise EnvironmentError("Cannot send transactions when network status is not connected.")

        gas_price: int = self.gas_price
        transaction_args: Dict[str, Any] = {
            "from": self.address,
            "nonce": self.nonce,
            "chainId": self.chain.value,
            "gasPrice": gas_price,
        }
        transaction_args.update(kwargs)
        transaction: Dict[str, Any] = contract_function.buildTransaction(transaction_args)
        if "gas" not in transaction:
            estimate_gas: int = 1000000
            try:
                estimate_gas = self._w3.eth.estimateGas(transaction)
            except ValueError:
                self.logger().error(f"Failed to estimate gas. Using default of 1000000.")
            transaction["gas"] = estimate_gas
        signed_transaction: AttributeDict = self._account.signTransaction(transaction)
        tx_hash: str = signed_transaction.hash.hex()
        self.schedule_eth_transaction(signed_transaction, gas_price)
        return tx_hash
Esempio n. 8
0
 def transact(self, contract_method: ContractFunction) -> TxReceipt:
     """A wrapper around to_be_called.transact() that waits until the transaction succeeds."""
     txhash = contract_method.transact(self.transaction)
     LOG.debug(f"Sending txHash={encode_hex(txhash)}")
     receipt, _ = check_successful_tx(web3=self.web3,
                                      txid=txhash,
                                      timeout=self.wait)
     return receipt
Esempio n. 9
0
    def build_transaction(
        self,
        contract_function: ContractFunction,
        sender_address: str,
        payload: dict = None,
        transaction_gas_limit: int = None,
    ) -> dict:

        #
        # Build
        #

        if not payload:
            payload = {}

        nonce = self.client.w3.eth.getTransactionCount(sender_address,
                                                       'pending')
        payload.update({
            'chainId': int(self.client.chain_id),
            'nonce': nonce,
            'from': sender_address,
            'gasPrice': self.client.gas_price
        })

        if transaction_gas_limit:
            payload['gas'] = int(transaction_gas_limit)

        # Get transaction type
        deployment = isinstance(contract_function, ContractConstructor)
        try:
            transaction_name = contract_function.fn_name.upper()
        except AttributeError:
            transaction_name = 'DEPLOY' if deployment else 'UNKNOWN'

        payload_pprint = dict(payload)
        payload_pprint['from'] = to_checksum_address(payload['from'])
        payload_pprint = ', '.join("{}: {}".format(k, v)
                                   for k, v in payload_pprint.items())
        self.log.debug(f"[TX-{transaction_name}] | {payload_pprint}")

        # Build transaction payload
        try:
            unsigned_transaction = contract_function.buildTransaction(payload)
        except (ValidationError, ValueError) as e:
            # TODO: #1504 - Handle validation failures for gas limits, invalid fields, etc.
            # Note: Geth raises ValueError in the same condition that pyevm raises ValidationError here.
            # Treat this condition as "Transaction Failed".
            error = str(e).replace("{", "").replace("}", "")  # See #724
            self.log.critical(f"Validation error: {error}")
            raise
        else:
            if deployment:
                self.log.info(
                    f"Deploying contract: {len(unsigned_transaction['data'])} bytes"
                )

        return unsigned_transaction
Esempio n. 10
0
 def build_and_send_tx(self,
                       func: ContractFunction,
                       tx_params: Optional[TxParams] = None) -> HexBytes:
     if not tx_params:
         tx_params = self.get_tx_params()
     transaction = func.buildTransaction(tx_params)
     signed_tx = self.w3.eth.account.sign_transaction(
         transaction, private_key=self.secrets._pk)
     try:
         return self.w3.eth.send_raw_transaction(signed_tx.rawTransaction)
     finally:
         self.last_nonce = Nonce(tx_params["nonce"] + 1)
Esempio n. 11
0
    def build_contract_transaction(
        self,
        contract_function: ContractFunction,
        sender_address: str,
        payload: dict = None,
        transaction_gas_limit: Optional[int] = None,
        gas_estimation_multiplier: Optional[float] = None,
        use_pending_nonce: Optional[bool] = None,
    ) -> dict:

        # Sanity checks for the gas estimation multiplier
        if gas_estimation_multiplier is not None:
            if not 1 <= gas_estimation_multiplier <= 3:  # TODO: Arbitrary upper bound.
                raise ValueError(
                    f"The gas estimation multiplier should be a float between 1 and 3, "
                    f"but we received {gas_estimation_multiplier}.")
            elif transaction_gas_limit is not None:
                raise ValueError(
                    "'transaction_gas_limit' and 'gas_estimation_multiplier' can't be used together."
                )

        payload = self.build_payload(
            sender_address=sender_address,
            payload=payload,
            transaction_gas_limit=transaction_gas_limit,
            use_pending_nonce=use_pending_nonce)
        self.__log_transaction(transaction_dict=payload,
                               contract_function=contract_function)
        try:
            transaction_dict = contract_function.buildTransaction(
                payload)  # Gas estimation occurs here
        except (TestTransactionFailed, ValidationError, ValueError) as error:
            # Note: Geth raises ValueError in the same condition that pyevm raises ValidationError here.
            # Treat this condition as "Transaction Failed" during gas estimation.
            raise self._handle_failed_transaction(
                exception=error,
                transaction_dict=payload,
                contract_function=contract_function,
                logger=self.log)

        # Overestimate the transaction gas limit according to the gas estimation multiplier, if any
        if gas_estimation_multiplier:
            gas_estimation = transaction_dict['gas']
            overestimation = int(
                math.ceil(gas_estimation * gas_estimation_multiplier))
            self.log.debug(
                f"Gas limit for this TX was increased from {gas_estimation} to {overestimation}, "
                f"using a multiplier of {gas_estimation_multiplier}.")
            transaction_dict['gas'] = overestimation
            # TODO: What if we're going over the block limit? Not likely, but perhaps worth checking (NRN)

        return transaction_dict
    def __append_contract_functions(self, contract_address, contract_abi):
        self.__contract_functions = self.__filter_by_type(
            'function', contract_abi)

        for funcName in self.__contract_functions.keys():
            setattr(
                self, funcName,
                self.__decorateContractFunctions(
                    ContractFunction.factory(funcName,
                                             web3=self.web3,
                                             contract_abi=contract_abi,
                                             address=contract_address,
                                             function_identifier=funcName)))
Esempio n. 13
0
 def estimate_transaction_cost(self, contract_function: ContractFunction,
                               **kwargs) -> int:
     transaction_args: Dict[str, Any] = {
         "from": self.address,
         "nonce": self.nonce,
         "chainId": self.chain.value,
         "gasPrice": self.gas_price,
     }
     transaction_args.update(kwargs)
     transaction: Dict[str, Any] = contract_function.buildTransaction(
         transaction_args)
     gas_estimate = self._w3.eth.estimateGas(transaction)
     return gas_estimate * self.gas_price
Esempio n. 14
0
def checked_transact(
    web3: Web3,
    sender_address: Address,
    function_call: ContractFunction,
    task_name: str,
    wait_confirmation_interval: bool = True,
) -> TxReceipt:

    log.info(f"Starting: {task_name}")
    transaction_hash = function_call.transact({"from": sender_address})

    confirmation_msg = ""
    if wait_confirmation_interval:
        confirmation_msg = " and waiting for confirmation"
    click.secho(
        f"\nSending transaction{confirmation_msg}: {task_name}"
        f"\n\tSee {etherscan_url_for_txhash(web3.eth.chain_id, transaction_hash)}"
    )

    timeout = 60 * 10  # 10mins
    transaction_receipt = web3.eth.wait_for_transaction_receipt(transaction_hash, timeout, 1.0)
    if wait_confirmation_interval:
        while (
            "blockNumber" not in transaction_receipt
            or web3.eth.block_number
            < transaction_receipt["blockNumber"] + DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS
        ):
            transaction_receipt = web3.eth.wait_for_transaction_receipt(
                transaction_hash, timeout, 1.0
            )
            gevent.sleep(1)

    was_successful = transaction_receipt["status"] == 1

    if not was_successful:
        log.error(
            f"Failed: {task_name}\nPlease check that the account "
            f"{to_checksum_address(sender_address)} has sufficient funds.",
            receipt=transaction_receipt,
        )
        sys.exit(1)

    log.info(
        f"Finished: {task_name}",
        successful=was_successful,
        transaction_hash=to_hex(transaction_hash),
    )

    return transaction_receipt
Esempio n. 15
0
    def _build_payload_same_function(
        self,
        contract_function: ContractFunction,
        contract_addresses: Sequence[ChecksumAddress],
    ) -> Tuple[List[Tuple[ChecksumAddress, bytes]], List[List[Any]]]:
        targets_with_data = []
        output_types = []
        tx_data = HexBytes(contract_function._encode_transaction_data())
        for contract_address in contract_addresses:
            targets_with_data.append((contract_address, tx_data))
            output_types.append(
                [output["type"] for output in contract_function.abi["outputs"]]
            )

        return targets_with_data, output_types
Esempio n. 16
0
    def build_transaction(
        self,
        contract_function: ContractFunction,
        sender_address: str,
        payload: dict = None,
        transaction_gas_limit: int = None,
    ) -> dict:

        #
        # Build Payload
        #

        base_payload = {
            'chainId':
            int(self.client.chain_id),
            'nonce':
            self.client.w3.eth.getTransactionCount(sender_address, 'pending'),
            'from':
            sender_address,
            'gasPrice':
            self.client.gas_price
        }

        # Aggregate
        if not payload:
            payload = {}
        payload.update(base_payload)
        # Explicit gas override - will skip gas estimation in next operation.
        if transaction_gas_limit:
            payload['gas'] = int(transaction_gas_limit)

        #
        # Build Transaction
        #

        self.__log_transaction(transaction_dict=payload,
                               contract_function=contract_function)
        try:
            transaction_dict = contract_function.buildTransaction(
                payload)  # Gas estimation occurs here
        except (TestTransactionFailed, ValidationError, ValueError) as error:
            # Note: Geth raises ValueError in the same condition that pyevm raises ValidationError here.
            # Treat this condition as "Transaction Failed" during gas estimation.
            raise self.__transaction_failed(
                exception=error,
                transaction_dict=payload,
                contract_function=contract_function)
        return transaction_dict
Esempio n. 17
0
 def _build_and_send_tx(self,
                        function: ContractFunction,
                        tx_params: Optional[TxParams] = None) -> HexBytes:
     """Build and send a transaction."""
     if not tx_params:
         tx_params = self._get_tx_params()
     transaction = function.buildTransaction(tx_params)
     signed_txn = self.w3.eth.account.sign_transaction(
         transaction, private_key=self.private_key)
     # TODO: This needs to get more complicated if we want to support replacing a transaction
     # FIXME: This does not play nice if transactions are sent from other places using the same wallet.
     try:
         return self.w3.eth.sendRawTransaction(signed_txn.rawTransaction)
     finally:
         logger.debug(f"nonce: {tx_params['nonce']}")
         self.last_nonce = Nonce(tx_params["nonce"] + 1)
Esempio n. 18
0
def build_and_send_tx(function: ContractFunction,
                      tx_params: Optional[TxParams] = None) -> HexBytes:
    """
    Build and send a transaction.
    """
    if not tx_params:
        tx_params = get_tx_params()

    transaction = function.buildTransaction(tx_params)
    signed_txn = w3.eth.account.sign_transaction(transaction,
                                                 private_key=PRIVATE_KEY)

    logging.info(signed_txn.rawTransaction)

    # !--- Commented for test.
    # return w3.eth.sendRawTransaction(signed_txn.rawTransaction)
    return w3.eth.send_raw_transaction(signed_txn.rawTransaction)
Esempio n. 19
0
    def _build_and_send_tx(self,
                           function: ContractFunction,
                           tx_params: Optional[TxParams] = None) -> HexBytes:
        """Build and send a transaction."""
        if not tx_params:
            tx_params = self._get_tx_params()
        transaction = function.buildTransaction(tx_params)
        signed_txn = self.w3.eth.account.sign_transaction(
            transaction, private_key=self.private_key)
        try:
            return self.w3.eth.sendRawTransaction(signed_txn.rawTransaction)
        finally:
            print("Print TX = ",
                  self.w3.toHex(self.w3.keccak(signed_txn.rawTransaction)))
            logger.debug(f"nonce: {tx_params['nonce']}")
            self.last_nonce = Nonce(tx_params["nonce"] + 1)
            #LOGG TX TO JSON
            with open('transactions.json', 'r') as fp:
                data = json.load(fp)
            tx_input = tx_params
            data.append(tx_input)
            with open('transactions.json', 'w') as fp:
                json.dump(data, fp, indent=2)
            fp.close()
            last_tx = self.w3.toHex(self.w3.keccak(signed_txn.rawTransaction))

            hash = [{'hash': last_tx}]
            with open('hash.json', 'w') as fp:
                json.dump(hash, fp, indent=2)
            fp.close()

            print("Waiting for TX to Confirm")
            while True:
                try:
                    if self.w3.eth.getTransactionReceipt(last_tx) != None:
                        print("Transaction Confirmed!")
                        break
                    else:
                        print("waiting for tx to confirm")
                except Exception:
                    pass
                time.sleep(5)
Esempio n. 20
0
def checked_transact(
    web3: Web3, service_address: Address, function_call: ContractFunction, task_name: str
) -> None:
    log.info(f"Starting: {task_name}")
    transaction_hash = function_call.transact({"from": service_address})
    transaction_receipt = web3.eth.waitForTransactionReceipt(transaction_hash)
    was_successful = transaction_receipt["status"] == 1

    if not was_successful:
        log.error(
            f"Failed: {task_name}\nPlease check that the account "
            f"{to_checksum_address(service_address)} has sufficient funds.",
            receipt=transaction_receipt,
        )
        sys.exit(1)

    log.info(
        f"Finished: {task_name}",
        successful=was_successful,
        transaction_hash=to_hex(transaction_hash),
    )
Esempio n. 21
0
    def send_transaction(self,
                         contract_function: ContractFunction,
                         sender_address: str,
                         payload: dict = None,
                         ) -> dict:

        if self.transacting_power is READ_ONLY_INTERFACE:
            raise self.InterfaceError(str(READ_ONLY_INTERFACE))

        #
        # Build
        #

        if not payload:
            payload = {}

        nonce = self.client.w3.eth.getTransactionCount(sender_address)
        payload.update({'chainId': int(self.client.chain_id),
                        'nonce': nonce,
                        'from': sender_address,
                        'gasPrice': self.client.w3.eth.gasPrice})

        # Get interface name
        try:
            transaction_name = contract_function.fn_name.upper()
        except AttributeError:
            if isinstance(contract_function, ContractConstructor):
                transaction_name = 'DEPLOY'
            else:
                transaction_name = 'UNKNOWN'

        # Build transaction payload
        try:
            unsigned_transaction = contract_function.buildTransaction(payload)
        except ValidationError:
            # TODO: Handle validation failures for gas limits, invalid fields, etc.
            raise

        #
        # Broadcast
        #

        signed_raw_transaction = self.transacting_power.sign_transaction(unsigned_transaction)
        txhash = self.client.send_raw_transaction(signed_raw_transaction)
        self.log.debug(f"[TX-{transaction_name}] | {to_checksum_address(payload['from'])}")

        try:
            receipt = self.client.wait_for_receipt(txhash, timeout=self.TIMEOUT)
        except TimeExhausted:
            # TODO: Handle transaction timeout
            raise
        else:
            self.log.debug(f"[RECEIPT-{transaction_name}] | {receipt['transactionHash'].hex()}")

        #
        # Confirm
        #

        # Primary check
        deployment_status = receipt.get('status', UNKNOWN_TX_STATUS)
        if deployment_status is 0:
            failure = f"Transaction transmitted, but receipt returned status code 0. " \
                      f"Full receipt: \n {pprint.pformat(receipt, indent=2)}"
            raise self.InterfaceError(failure)

        if deployment_status is UNKNOWN_TX_STATUS:
            self.log.info(f"Unknown transaction status for {txhash} (receipt did not contain a status field)")

            # Secondary check TODO: Is this a sensible check?
            tx = self.client.get_transaction(txhash)
            if tx["gas"] == receipt["gasUsed"]:
                raise self.InterfaceError(f"Transaction consumed 100% of transaction gas."
                                          f"Full receipt: \n {pprint.pformat(receipt, indent=2)}")

        return receipt
Esempio n. 22
0
    def send_transaction(
        self,
        transaction_function: ContractFunction,
        sender_address: str,
        payload: dict = None,
    ) -> dict:

        if self.transacting_power is READ_ONLY_INTERFACE:
            raise self.InterfaceError(str(READ_ONLY_INTERFACE))

        #
        # Build
        #

        if not payload:
            payload = {}

        nonce = self.client.w3.eth.getTransactionCount(sender_address)
        payload.update({
            'chainId': int(self.client.chain_id),
            'nonce': nonce,
            'from': sender_address,
            'gasPrice': self.client.w3.eth.gasPrice
        })

        unsigned_transaction = transaction_function.buildTransaction(payload)

        #
        # Broadcast
        #

        signed_raw_transaction = self.transacting_power.sign_transaction(
            unsigned_transaction)
        txhash = self.client.send_raw_transaction(signed_raw_transaction)

        try:
            receipt = self.client.wait_for_receipt(txhash,
                                                   timeout=self.TIMEOUT)
        except TimeExhausted:
            raise

        #
        # Confirm
        #

        # Primary check
        deployment_status = receipt.get('status', UNKNOWN_TX_STATUS)
        if deployment_status is 0:
            failure = f"Transaction transmitted, but receipt returned status code 0. " \
                      f"Full receipt: \n {pprint.pformat(receipt, indent=2)}"
            raise self.InterfaceError(failure)

        if deployment_status is UNKNOWN_TX_STATUS:
            self.log.info(
                f"Unknown transaction status for {txhash} (receipt did not contain a status field)"
            )

            # Secondary check TODO: Is this a sensible check?
            tx = self.client.get_transaction(txhash)
            if tx["gas"] == receipt["gasUsed"]:
                raise self.InterfaceError(
                    f"Transaction consumed 100% of transaction gas."
                    f"Full receipt: \n {pprint.pformat(receipt, indent=2)}")

        return receipt
Esempio n. 23
0
    def send_transaction(
        self,
        contract_function: ContractFunction,
        sender_address: str,
        payload: dict = None,
        transaction_gas_limit: int = None,
    ) -> dict:

        if self.transacting_power is READ_ONLY_INTERFACE:
            raise self.InterfaceError(str(READ_ONLY_INTERFACE))

        #
        # Build
        #

        if not payload:
            payload = {}

        nonce = self.client.w3.eth.getTransactionCount(sender_address,
                                                       'pending')
        payload.update({
            'chainId': int(self.client.chain_id),
            'nonce': nonce,
            'from': sender_address,
            'gasPrice': self.client.gas_price
        })

        if transaction_gas_limit:
            payload['gas'] = int(transaction_gas_limit)

        # Get interface name
        deployment = True if isinstance(contract_function,
                                        ContractConstructor) else False

        try:
            transaction_name = contract_function.fn_name.upper()
        except AttributeError:
            if deployment:
                transaction_name = 'DEPLOY'
            else:
                transaction_name = 'UNKNOWN'

        payload_pprint = dict(payload)
        payload_pprint['from'] = to_checksum_address(payload['from'])
        payload_pprint = ', '.join("{}: {}".format(k, v)
                                   for k, v in payload_pprint.items())
        self.log.debug(f"[TX-{transaction_name}] | {payload_pprint}")

        # Build transaction payload
        try:
            unsigned_transaction = contract_function.buildTransaction(payload)
        except (ValidationError, ValueError) as e:
            # TODO: #1504 - Handle validation failures for gas limits, invalid fields, etc.
            # Note: Geth raises ValueError in the same condition that pyevm raises ValidationError here.
            # Treat this condition as "Transaction Failed".
            error = str(e).replace("{", "{{").replace("}", "}}")  # See #724
            self.log.critical(f"Validation error: {error}")
            raise
        else:
            if deployment:
                self.log.info(
                    f"Deploying contract: {len(unsigned_transaction['data'])} bytes"
                )

        #
        # Broadcast
        #

        signed_raw_transaction = self.transacting_power.sign_transaction(
            unsigned_transaction)
        txhash = self.client.send_raw_transaction(signed_raw_transaction)

        try:
            receipt = self.client.wait_for_receipt(txhash,
                                                   timeout=self.TIMEOUT)
        except TimeExhausted:
            # TODO: #1504 - Handle transaction timeout
            raise
        else:
            self.log.debug(
                f"[RECEIPT-{transaction_name}] | txhash: {receipt['transactionHash'].hex()}"
            )

        #
        # Confirm
        #

        # Primary check
        deployment_status = receipt.get('status', UNKNOWN_TX_STATUS)
        if deployment_status == 0:
            failure = f"Transaction transmitted, but receipt returned status code 0. " \
                      f"Full receipt: \n {pprint.pformat(receipt, indent=2)}"
            raise self.InterfaceError(failure)

        if deployment_status is UNKNOWN_TX_STATUS:
            self.log.info(
                f"Unknown transaction status for {txhash} (receipt did not contain a status field)"
            )

            # Secondary check
            tx = self.client.get_transaction(txhash)
            if tx["gas"] == receipt["gasUsed"]:
                raise self.InterfaceError(
                    f"Transaction consumed 100% of transaction gas."
                    f"Full receipt: \n {pprint.pformat(receipt, indent=2)}")

        return receipt