Skip to content

Commit

Permalink
Reduce amount of codegen by moving helper functions to base contract …
Browse files Browse the repository at this point in the history
…function class. Includes version bump to v0.0.47 (#148)

Includes a version pin of web3py due to breaking changes:
#149
  • Loading branch information
slundqui authored Nov 7, 2024
1 parent 54bca18 commit 02d6973
Show file tree
Hide file tree
Showing 75 changed files with 2,863 additions and 12,407 deletions.
3,871 changes: 1,154 additions & 2,717 deletions example/types/Example/ExampleContract.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion example/types/Example/ExampleTypes.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Dataclasses for all structs in the Example contract.
DO NOT EDIT. This file was generated by pypechain v0.0.46.
DO NOT EDIT. This file was generated by pypechain v0.0.47.
See documentation at https://github.com/delvtech/pypechain """

# super() call methods are generic, while our version adds values & types
Expand Down
2 changes: 1 addition & 1 deletion example/types/Example/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Export all types from generated files.
DO NOT EDIT. This file was generated by pypechain v0.0.46.
DO NOT EDIT. This file was generated by pypechain v0.0.47.
See documentation at https://github.com/delvtech/pypechain """

# The module name reflects that of the solidity contract,
Expand Down
2 changes: 1 addition & 1 deletion example/types/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Export all types from generated files.
DO NOT EDIT. This file was generated by pypechain v0.0.46.
DO NOT EDIT. This file was generated by pypechain v0.0.47.
See documentation at https://github.com/delvtech/pypechain """

# The module name reflects that of the solidity contract,
Expand Down
2 changes: 1 addition & 1 deletion example/types/pypechain.version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
pypechain == 0.0.46
pypechain == 0.0.47
7 changes: 1 addition & 6 deletions pypechain/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@

from .base_event import BaseEvent, BaseEventArgs
from .combomethod_typed import combomethod_typed
from .contract_call_exception import (
FailedTransaction,
PypechainCallException,
check_txn_receipt,
handle_contract_logic_error,
)
from .contract_call_exception import FailedTransaction, PypechainCallException
from .contract_function import PypechainContractFunction
from .error import ErrorInfo, ErrorParams, PypechainBaseContractErrors, PypechainBaseError
from .utilities import (
Expand Down
7 changes: 4 additions & 3 deletions pypechain/core/contract_call_exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@
from __future__ import annotations

import copy
from typing import Any, Generic, Literal, Type, TypeVar, Union, cast
from typing import TYPE_CHECKING, Any, Generic, Literal, Type, TypeVar, Union, cast

from eth_typing import BlockNumber
from hexbytes import HexBytes
from web3.exceptions import ContractCustomError, ContractLogicError, ContractPanicError, OffchainLookup
from web3.types import BlockIdentifier, TxParams, TxReceipt

from .contract_function import PypechainContractFunction
from .error import PypechainBaseContractErrors
if TYPE_CHECKING:
from .contract_function import PypechainContractFunction
from .error import PypechainBaseContractErrors

# pylint: disable=too-many-arguments
# pylint: disable=too-many-positional-arguments
Expand Down
182 changes: 182 additions & 0 deletions pypechain/core/contract_function.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
"""The contract function base class for pypechain."""

from __future__ import annotations

from typing import Any, Type

from eth_account.signers.local import LocalAccount
from hexbytes import HexBytes
from web3.contract.contract import ContractFunction
from web3.types import BlockIdentifier, StateOverride, TxParams, TxReceipt

from .contract_call_exception import check_txn_receipt, handle_contract_logic_error
from .error import PypechainBaseContractErrors


class PypechainContractFunction(ContractFunction):
Expand All @@ -11,3 +21,175 @@ class PypechainContractFunction(ContractFunction):

# The python type signatures for the contract function
_type_signature: str

# The error class for this function
_error_class: Type[PypechainBaseContractErrors]

# TODO abstract out `call`
# We still need to codegen the return types of call, but we want to handle errors
# here, so we use a private function
def _call(
self,
transaction: TxParams | None,
block_identifier: BlockIdentifier,
state_override: StateOverride | None,
ccip_read_enabled: bool | None,
) -> Any:
try:
raw_values = super().call(transaction, block_identifier, state_override, ccip_read_enabled)
except Exception as err: # pylint disable=broad-except
raise handle_contract_logic_error(
contract_function=self,
errors_class=self._error_class,
err=err,
contract_call_type="call",
transaction=transaction,
block_identifier=block_identifier,
) from err
return raw_values

def transact(self, transaction: TxParams | None = None) -> HexBytes:
try:
return super().transact(transaction)
except Exception as err: # pylint disable=broad-except
raise handle_contract_logic_error(
contract_function=self,
errors_class=self._error_class,
err=err,
contract_call_type="transact",
transaction=transaction,
block_identifier="pending", # race condition here, best effort to get block of txn.
) from err

def estimate_gas(
self,
transaction: TxParams | None = None,
block_identifier: BlockIdentifier | None = None,
state_override: StateOverride | None = None,
) -> int:
try:
return super().estimate_gas(transaction, block_identifier, state_override)
except Exception as err: # pylint disable=broad-except
raise handle_contract_logic_error(
contract_function=self,
errors_class=self._error_class,
err=err,
contract_call_type="build",
transaction=transaction,
block_identifier="pending", # race condition here, best effort to get block of txn.
) from err

def build_transaction(self, transaction: TxParams | None = None) -> TxParams:
try:
return super().build_transaction(transaction)
except Exception as err: # pylint disable=broad-except
raise handle_contract_logic_error(
contract_function=self,
errors_class=self._error_class,
err=err,
contract_call_type="build",
transaction=transaction,
block_identifier="pending", # race condition here, best effort to get block of txn.
) from err

def sign_and_transact(self, account: LocalAccount, transaction: TxParams | None = None) -> HexBytes:
"""Convenience method for signing and sending a transaction using the provided account.
Arguments
---------
account : LocalAccount
The account to use for signing and sending the transaction.
transaction : TxParams | None, optional
The transaction parameters to use for sending the transaction.
Returns
-------
HexBytes
The transaction hash.
"""
if transaction is None:
transaction_params: TxParams = {}
else:
transaction_params: TxParams = transaction

if "from" in transaction_params:
# Ensure if transaction is set, it matches
assert (
transaction_params["from"] == account.address
), f"Transaction from {transaction_params['from']} does not match account {account.address}"
else:
transaction_params["from"] = account.address

if "gas" not in transaction_params:
# Web3 default gas estimate seems to be underestimating gas, likely due to
# not looking at pending block. Here, we explicitly call estimate gas
# if gas isn't passed in.
transaction_params["gas"] = self.estimate_gas(transaction_params, block_identifier="pending")

# Build the raw transaction
raw_transaction = self.build_transaction(transaction_params)

if "nonce" not in raw_transaction:
raw_transaction["nonce"] = self.w3.eth.get_transaction_count(account.address, block_identifier="pending")

# Sign the raw transaction
# Mismatched types between account and web3py
signed_transaction = account.sign_transaction(raw_transaction) # type: ignore

# Send the signed transaction
try:
return self.w3.eth.send_raw_transaction(signed_transaction.raw_transaction)
except Exception as err: # pylint disable=broad-except
raise handle_contract_logic_error(
contract_function=self,
errors_class=self._error_class,
err=err,
contract_call_type="transact",
transaction=transaction_params,
block_identifier="pending", # race condition here, best effort to get block of txn.
) from err

def sign_transact_and_wait(
self,
account: LocalAccount,
transaction: TxParams | None = None,
timeout: float | None = None,
poll_latency: float | None = None,
validate_transaction: bool = False,
) -> TxReceipt:
"""Convenience method for signing and sending a transaction using the provided account.
Arguments
---------
account : LocalAccount
The account to use for signing and sending the transaction.
transaction : TxParams | None, optional
The transaction parameters to use for sending the transaction.
timeout: float, optional
The number of seconds to wait for the transaction to be mined. Defaults to 120.
poll_latency: float, optional
The number of seconds to wait between polling for the transaction receipt. Defaults to 0.1.
validate_transaction: bool, optional
Whether to validate the transaction. If True, will throw an exception if the resulting
tx_receipt returned a failure status.
Returns
-------
HexBytes
The transaction hash.
"""

# pylint: disable=too-many-arguments
# pylint: disable=too-many-positional-arguments

if timeout is None:
timeout = 120
if poll_latency is None:
poll_latency = 0.1

tx_hash = self.sign_and_transact(account, transaction)
tx_receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash, timeout=timeout, poll_latency=poll_latency)
# Check the receipt, throwing an error if status == 0
if validate_transaction:
return check_txn_receipt(self, tx_hash, tx_receipt)
return tx_receipt
10 changes: 3 additions & 7 deletions pypechain/templates/contract.py/base.py.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,6 @@ from pypechain.core import (
PypechainContractFunction,
PypechainContractLogicError,
PypechainBaseContractErrors,
handle_contract_logic_error,
check_txn_receipt,
)


Expand All @@ -103,14 +101,12 @@ structs = {
{% endif %}
{%- endfor %}}

{{abi_block}}

{{ errors_block }}

{{functions_block}}

{% if has_events %}{{ events_block }}{% endif %}


{{abi_block}}

{{ errors_block }}

{{contract_block}}
Loading

0 comments on commit 02d6973

Please sign in to comment.