Skip to content

Feature/asyncify contract #2394

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 30 commits into from
Mar 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0a898fe
init commit of async contract
dbfreem Mar 4, 2022
04bf711
more linting, still more to go
dbfreem Mar 4, 2022
e4dface
more linting. I seem to have broken some of the test on this one.
dbfreem Mar 4, 2022
acdae38
fixed broken test
dbfreem Mar 6, 2022
3c63696
This is most of the linting changes so far
dbfreem Mar 7, 2022
7118d24
async test
dbfreem Mar 7, 2022
e15971e
lint rearrange imports
dbfreem Mar 7, 2022
3682aa3
ContractEvents/ContractEvent Async
dbfreem Mar 7, 2022
b74662e
couple more fixes
dbfreem Mar 7, 2022
2944ee9
more work on contract
dbfreem Mar 8, 2022
0064b2a
Merge branch 'asyncify-contract' of https://github.com/ethereum/web3.…
dbfreem Mar 9, 2022
d568a5e
continued work on contract and fixed linting
dbfreem Mar 9, 2022
31c1e34
fixes
dbfreem Mar 9, 2022
3c2e26b
linting
dbfreem Mar 9, 2022
722c711
fix test import in the wrong place
dbfreem Mar 9, 2022
79f99dd
fixing combomethod
dbfreem Mar 9, 2022
ba4ef33
Merge branch 'asyncify-contract' of https://github.com/ethereum/web3.…
dbfreem Mar 10, 2022
a2170e6
First test added
dbfreem Mar 10, 2022
1c765e8
ContractConstructor
dbfreem Mar 10, 2022
6e2ee8a
linting fixes
dbfreem Mar 11, 2022
ad97b6e
eth.contract and linting
dbfreem Mar 11, 2022
0d723dd
async_parse_block_identifier_int
dbfreem Mar 11, 2022
f1639b8
Merge branch 'asyncify-contract' of https://github.com/ethereum/web3.…
dbfreem Mar 15, 2022
1dc9a62
added two more async functions
dbfreem Mar 15, 2022
fa7d429
fill_transaction_defaults async for later use in build_transaction_fo…
dbfreem Mar 18, 2022
fdc6d50
build_transaction in ContractFunction
dbfreem Mar 19, 2022
dafa820
merge
dbfreem Mar 20, 2022
51248eb
estimateGas async in Contract and docs
dbfreem Mar 22, 2022
814d16b
lint
dbfreem Mar 22, 2022
268fe52
docs
dbfreem Mar 22, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 26 additions & 8 deletions docs/contracts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,11 @@ Each Contract Factory exposes the following methods.
.. py:classmethod:: Contract.constructor(*args, **kwargs).estimateGas(transaction=None, block_identifier=None)
:noindex:

.. warning:: Deprecated: This method is deprecated in favor of :py:meth:`Contract.constructor(*args, **kwargs).estimate_gas`

.. py:classmethod:: Contract.constructor(*args, **kwargs).estimate_gas(transaction=None, block_identifier=None)
:noindex:

Estimate gas for constructing and deploying the contract.

This method behaves the same as the
Expand All @@ -264,12 +269,17 @@ Each Contract Factory exposes the following methods.

.. code-block:: python

>>> token_contract.constructor(web3.eth.coinbase, 12345).estimateGas()
>>> token_contract.constructor(web3.eth.coinbase, 12345).estimate_gas()
12563

.. py:classmethod:: Contract.constructor(*args, **kwargs).buildTransaction(transaction=None)
:noindex:

.. warning:: Deprecated: This method is deprecated in favor of :py:meth:`Contract.constructor(*args, **kwargs).build_transaction`

.. py:classmethod:: Contract.constructor(*args, **kwargs).build_transaction(transaction=None)
:noindex:

Construct the contract deploy transaction bytecode data.

If the contract takes constructor parameters they should be provided as
Expand All @@ -286,7 +296,7 @@ Each Contract Factory exposes the following methods.
'gasPrice': w3.eth.gas_price,
'chainId': None
}
>>> contract_data = token_contract.constructor(web3.eth.coinbase, 12345).buildTransaction(transaction)
>>> contract_data = token_contract.constructor(web3.eth.coinbase, 12345).build_transaction(transaction)
>>> web3.eth.send_transaction(contract_data)

.. _contract_createFilter:
Expand Down Expand Up @@ -835,14 +845,18 @@ Methods

.. py:method:: ContractFunction.estimateGas(transaction, block_identifier=None)

.. warning:: Deprecated: This method is deprecated in favor of :class:`~estimate_gas`

.. py:method:: ContractFunction.estimate_gas(transaction, block_identifier=None)

Call a contract function, executing the transaction locally using the
``eth_call`` API. This will not create a new public transaction.

Refer to the following invocation:

.. code-block:: python

myContract.functions.myMethod(*args, **kwargs).estimateGas(transaction)
myContract.functions.myMethod(*args, **kwargs).estimate_gas(transaction)

This method behaves the same as the :py:meth:`ContractFunction.transact` method,
with transaction details being passed into the end portion of the
Expand All @@ -853,7 +867,7 @@ Methods

.. code-block:: python

>>> my_contract.functions.multiply7(3).estimateGas()
>>> my_contract.functions.multiply7(3).estimate_gas()
42650

.. note::
Expand All @@ -863,13 +877,17 @@ Methods

.. py:method:: ContractFunction.buildTransaction(transaction)

.. warning:: Deprecated: This method is deprecated in favor of :class:`~build_transaction`

.. py:method:: ContractFunction.build_transaction(transaction)

Builds a transaction dictionary based on the contract function call specified.

Refer to the following invocation:

.. code-block:: python

myContract.functions.myMethod(*args, **kwargs).buildTransaction(transaction)
myContract.functions.myMethod(*args, **kwargs).build_transaction(transaction)

This method behaves the same as the :py:meth:`Contract.transact` method,
with transaction details being passed into the end portion of the
Expand All @@ -881,15 +899,15 @@ Methods

.. code-block:: python

>>> math_contract.functions.increment(5).buildTransaction({'nonce': 10})
>>> math_contract.functions.increment(5).build_transaction({'nonce': 10})

You may use :meth:`~web3.eth.Eth.getTransactionCount` to get the current nonce
for an account. Therefore a shortcut for producing a transaction dictionary with
nonce included looks like:

.. code-block:: python

>>> math_contract.functions.increment(5).buildTransaction({'nonce': web3.eth.get_transaction_count('0xF5...')})
>>> math_contract.functions.increment(5).build_transaction({'nonce': web3.eth.get_transaction_count('0xF5...')})

Returns a transaction dictionary. This transaction dictionary can then be sent using
:meth:`~web3.eth.Eth.send_transaction`.
Expand All @@ -899,7 +917,7 @@ Methods

.. code-block:: python

>>> math_contract.functions.increment(5).buildTransaction({'maxFeePerGas': 2000000000, 'maxPriorityFeePerGas': 1000000000})
>>> math_contract.functions.increment(5).build_transaction({'maxFeePerGas': 2000000000, 'maxPriorityFeePerGas': 1000000000})
{
'to': '0x6Bc272FCFcf89C14cebFC57B8f1543F5137F97dE',
'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005',
Expand Down
6 changes: 6 additions & 0 deletions docs/providers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,12 @@ Geth
- :meth:`web3.geth.txpool.content() <web3.geth.txpool.TxPool.content()>`
- :meth:`web3.geth.txpool.status() <web3.geth.txpool.TxPool.status()>`

Contract
^^^^^^^^
Contract is fully implemented for the Async provider. The only documented exception to this at
the moment is where :class:`ENS` is needed for address lookup. All addresses that are passed to Async
contract should not be :class:`ENS` addresses.

Supported Middleware
^^^^^^^^^^^^^^^^^^^^
- :meth:`Gas Price Strategy <web3.middleware.gas_price_strategy_middleware>`
Expand Down
18 changes: 18 additions & 0 deletions tests/core/conftest.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import pytest

import pytest_asyncio

from web3 import Web3
from web3.eth import (
AsyncEth,
)
from web3.module import (
Module,
)
from web3.providers.eth_tester.main import (
AsyncEthereumTesterProvider,
)

# --- inherit from `web3.module.Module` class --- #

Expand Down Expand Up @@ -97,3 +106,12 @@ def __init__(self, a, b):
self.a = a
self.b = b
return ModuleManyArgs


@pytest_asyncio.fixture()
async def async_w3():
provider = AsyncEthereumTesterProvider()
w3 = Web3(provider, modules={'eth': [AsyncEth]},
middlewares=provider.middlewares)
w3.eth.default_account = await w3.eth.coinbase
return w3
16 changes: 0 additions & 16 deletions tests/core/contracts/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
)
import pytest_asyncio

from web3 import Web3
from web3._utils.module_testing.emitter_contract import (
CONTRACT_EMITTER_ABI,
CONTRACT_EMITTER_CODE,
Expand Down Expand Up @@ -54,12 +53,6 @@
from web3.contract import (
AsyncContract,
)
from web3.eth import (
AsyncEth,
)
from web3.providers.eth_tester.main import (
AsyncEthereumTesterProvider,
)

CONTRACT_NESTED_TUPLE_SOURCE = """
pragma solidity >=0.4.19 <0.6.0;
Expand Down Expand Up @@ -1064,15 +1057,6 @@ async def async_deploy(async_web3, Contract, apply_func=identity, args=None):
return contract


@pytest_asyncio.fixture()
async def async_w3():
provider = AsyncEthereumTesterProvider()
w3 = Web3(provider, modules={'eth': [AsyncEth]},
middlewares=provider.middlewares)
w3.eth.default_account = await w3.eth.coinbase
return w3


@pytest_asyncio.fixture()
def AsyncMathContract(async_w3, MATH_ABI, MATH_CODE, MATH_RUNTIME):
contract = AsyncContract.factory(async_w3,
Expand Down
22 changes: 22 additions & 0 deletions tests/core/utilities/test_async_transaction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import pytest

from web3._utils.async_transactions import (
fill_transaction_defaults,
)


@pytest.mark.asyncio()
async def test_fill_transaction_defaults_for_all_params(async_w3):
default_transaction = await fill_transaction_defaults(async_w3, {})

block = await async_w3.eth.get_block('latest')
assert default_transaction == {
'chainId': await async_w3.eth.chain_id,
'data': b'',
'gas': await async_w3.eth.estimate_gas({}),
'maxFeePerGas': (
await async_w3.eth.max_priority_fee + (2 * block['baseFeePerGas'])
),
'maxPriorityFeePerGas': await async_w3.eth.max_priority_fee,
'value': 0,
}
79 changes: 79 additions & 0 deletions web3/_utils/async_transactions.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,63 @@
from typing import (
TYPE_CHECKING,
Awaitable,
Optional,
cast,
)

from eth_utils.toolz import (
curry,
merge,
)

from web3._utils.utility_methods import (
any_in_dict,
)
from web3.constants import (
DYNAMIC_FEE_TXN_PARAMS,
)
from web3.types import (
BlockIdentifier,
TxParams,
Wei,
)

if TYPE_CHECKING:
from web3 import Web3 # noqa: F401
from web3.eth import AsyncEth # noqa: F401


async def _estimate_gas(w3: 'Web3', tx: TxParams) -> Awaitable[int]:
return await w3.eth.estimate_gas(tx) # type: ignore


async def _gas_price(w3: 'Web3', tx: TxParams) -> Awaitable[Optional[Wei]]:
return await w3.eth.generate_gas_price(tx) or w3.eth.gas_price # type: ignore


async def _max_fee_per_gas(w3: 'Web3', tx: TxParams) -> Awaitable[Wei]:
block = await w3.eth.get_block('latest') # type: ignore
return await w3.eth.max_priority_fee + (2 * block['baseFeePerGas']) # type: ignore


async def _max_priority_fee_gas(w3: 'Web3', tx: TxParams) -> Awaitable[Wei]:
return await w3.eth.max_priority_fee # type: ignore


async def _chain_id(w3: 'Web3', tx: TxParams) -> Awaitable[int]:
return await w3.eth.chain_id # type: ignore

TRANSACTION_DEFAULTS = {
'value': 0,
'data': b'',
'gas': _estimate_gas,
'gasPrice': _gas_price,
'maxFeePerGas': _max_fee_per_gas,
'maxPriorityFeePerGas': _max_priority_fee_gas,
'chainId': _chain_id,
}


async def get_block_gas_limit(
web3_eth: "AsyncEth", block_identifier: Optional[BlockIdentifier] = None
) -> int:
Expand All @@ -40,3 +84,38 @@ async def get_buffered_gas_estimate(
)

return min(gas_limit, gas_estimate + gas_buffer)


@curry
async def fill_transaction_defaults(w3: "Web3", transaction: TxParams) -> TxParams:
"""
if w3 is None, fill as much as possible while offline
"""
strategy_based_gas_price = await w3.eth.generate_gas_price(transaction) # type: ignore
is_dynamic_fee_transaction = (
not strategy_based_gas_price
and (
'gasPrice' not in transaction # default to dynamic fee transaction
or any_in_dict(DYNAMIC_FEE_TXN_PARAMS, transaction)
)
)

defaults = {}
for key, default_getter in TRANSACTION_DEFAULTS.items():
if key not in transaction:
if (
is_dynamic_fee_transaction and key == 'gasPrice'
or not is_dynamic_fee_transaction and key in DYNAMIC_FEE_TXN_PARAMS
):
# do not set default max fees if legacy txn or gas price if dynamic fee txn
continue

if callable(default_getter):
if w3 is None:
raise ValueError(f"You must specify a '{key}' value in the transaction")
default_val = await default_getter(w3, transaction)
else:
default_val = default_getter

defaults[key] = default_val
return merge(defaults, transaction)
Loading