Skip to content

Commit 1814e11

Browse files
dbfreempacrob
authored and
pacrob
committed
Feature/asyncify contract (ethereum#2394)
* fill_transaction_defaults async for later use in build_transaction_for_function * build_transaction in ContractFunction * estimateGas async in Contract and docs
1 parent 7918026 commit 1814e11

File tree

7 files changed

+257
-51
lines changed

7 files changed

+257
-51
lines changed

docs/contracts.rst

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,11 @@ Each Contract Factory exposes the following methods.
247247
.. py:classmethod:: Contract.constructor(*args, **kwargs).estimateGas(transaction=None, block_identifier=None)
248248
:noindex:
249249

250+
.. warning:: Deprecated: This method is deprecated in favor of :py:meth:`Contract.constructor(*args, **kwargs).estimate_gas`
251+
252+
.. py:classmethod:: Contract.constructor(*args, **kwargs).estimate_gas(transaction=None, block_identifier=None)
253+
:noindex:
254+
250255
Estimate gas for constructing and deploying the contract.
251256

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

265270
.. code-block:: python
266271
267-
>>> token_contract.constructor(web3.eth.coinbase, 12345).estimateGas()
272+
>>> token_contract.constructor(web3.eth.coinbase, 12345).estimate_gas()
268273
12563
269274
270275
.. py:classmethod:: Contract.constructor(*args, **kwargs).buildTransaction(transaction=None)
271276
:noindex:
272277

278+
.. warning:: Deprecated: This method is deprecated in favor of :py:meth:`Contract.constructor(*args, **kwargs).build_transaction`
279+
280+
.. py:classmethod:: Contract.constructor(*args, **kwargs).build_transaction(transaction=None)
281+
:noindex:
282+
273283
Construct the contract deploy transaction bytecode data.
274284

275285
If the contract takes constructor parameters they should be provided as
@@ -286,7 +296,7 @@ Each Contract Factory exposes the following methods.
286296
'gasPrice': w3.eth.gas_price,
287297
'chainId': None
288298
}
289-
>>> contract_data = token_contract.constructor(web3.eth.coinbase, 12345).buildTransaction(transaction)
299+
>>> contract_data = token_contract.constructor(web3.eth.coinbase, 12345).build_transaction(transaction)
290300
>>> web3.eth.send_transaction(contract_data)
291301
292302
.. _contract_createFilter:
@@ -835,14 +845,18 @@ Methods
835845

836846
.. py:method:: ContractFunction.estimateGas(transaction, block_identifier=None)
837847
848+
.. warning:: Deprecated: This method is deprecated in favor of :class:`~estimate_gas`
849+
850+
.. py:method:: ContractFunction.estimate_gas(transaction, block_identifier=None)
851+
838852
Call a contract function, executing the transaction locally using the
839853
``eth_call`` API. This will not create a new public transaction.
840854

841855
Refer to the following invocation:
842856

843857
.. code-block:: python
844858
845-
myContract.functions.myMethod(*args, **kwargs).estimateGas(transaction)
859+
myContract.functions.myMethod(*args, **kwargs).estimate_gas(transaction)
846860
847861
This method behaves the same as the :py:meth:`ContractFunction.transact` method,
848862
with transaction details being passed into the end portion of the
@@ -853,7 +867,7 @@ Methods
853867

854868
.. code-block:: python
855869
856-
>>> my_contract.functions.multiply7(3).estimateGas()
870+
>>> my_contract.functions.multiply7(3).estimate_gas()
857871
42650
858872
859873
.. note::
@@ -863,13 +877,17 @@ Methods
863877

864878
.. py:method:: ContractFunction.buildTransaction(transaction)
865879
880+
.. warning:: Deprecated: This method is deprecated in favor of :class:`~build_transaction`
881+
882+
.. py:method:: ContractFunction.build_transaction(transaction)
883+
866884
Builds a transaction dictionary based on the contract function call specified.
867885

868886
Refer to the following invocation:
869887

870888
.. code-block:: python
871889
872-
myContract.functions.myMethod(*args, **kwargs).buildTransaction(transaction)
890+
myContract.functions.myMethod(*args, **kwargs).build_transaction(transaction)
873891
874892
This method behaves the same as the :py:meth:`Contract.transact` method,
875893
with transaction details being passed into the end portion of the
@@ -881,15 +899,15 @@ Methods
881899

882900
.. code-block:: python
883901
884-
>>> math_contract.functions.increment(5).buildTransaction({'nonce': 10})
902+
>>> math_contract.functions.increment(5).build_transaction({'nonce': 10})
885903
886904
You may use :meth:`~web3.eth.Eth.getTransactionCount` to get the current nonce
887905
for an account. Therefore a shortcut for producing a transaction dictionary with
888906
nonce included looks like:
889907

890908
.. code-block:: python
891909
892-
>>> math_contract.functions.increment(5).buildTransaction({'nonce': web3.eth.get_transaction_count('0xF5...')})
910+
>>> math_contract.functions.increment(5).build_transaction({'nonce': web3.eth.get_transaction_count('0xF5...')})
893911
894912
Returns a transaction dictionary. This transaction dictionary can then be sent using
895913
:meth:`~web3.eth.Eth.send_transaction`.
@@ -899,7 +917,7 @@ Methods
899917

900918
.. code-block:: python
901919
902-
>>> math_contract.functions.increment(5).buildTransaction({'maxFeePerGas': 2000000000, 'maxPriorityFeePerGas': 1000000000})
920+
>>> math_contract.functions.increment(5).build_transaction({'maxFeePerGas': 2000000000, 'maxPriorityFeePerGas': 1000000000})
903921
{
904922
'to': '0x6Bc272FCFcf89C14cebFC57B8f1543F5137F97dE',
905923
'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005',

docs/providers.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,12 @@ Geth
468468
- :meth:`web3.geth.txpool.content() <web3.geth.txpool.TxPool.content()>`
469469
- :meth:`web3.geth.txpool.status() <web3.geth.txpool.TxPool.status()>`
470470

471+
Contract
472+
^^^^^^^^
473+
Contract is fully implemented for the Async provider. The only documented exception to this at
474+
the moment is where :class:`ENS` is needed for address lookup. All addresses that are passed to Async
475+
contract should not be :class:`ENS` addresses.
476+
471477
Supported Middleware
472478
^^^^^^^^^^^^^^^^^^^^
473479
- :meth:`Gas Price Strategy <web3.middleware.gas_price_strategy_middleware>`

tests/core/conftest.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
import pytest
22

3+
import pytest_asyncio
4+
5+
from web3 import Web3
6+
from web3.eth import (
7+
AsyncEth,
8+
)
39
from web3.module import (
410
Module,
511
)
12+
from web3.providers.eth_tester.main import (
13+
AsyncEthereumTesterProvider,
14+
)
615

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

@@ -97,3 +106,12 @@ def __init__(self, a, b):
97106
self.a = a
98107
self.b = b
99108
return ModuleManyArgs
109+
110+
111+
@pytest_asyncio.fixture()
112+
async def async_w3():
113+
provider = AsyncEthereumTesterProvider()
114+
w3 = Web3(provider, modules={'eth': [AsyncEth]},
115+
middlewares=provider.middlewares)
116+
w3.eth.default_account = await w3.eth.coinbase
117+
return w3

tests/core/contracts/conftest.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
)
1111
import pytest_asyncio
1212

13-
from web3 import Web3
1413
from web3._utils.module_testing.emitter_contract import (
1514
CONTRACT_EMITTER_ABI,
1615
CONTRACT_EMITTER_CODE,
@@ -54,12 +53,6 @@
5453
from web3.contract import (
5554
AsyncContract,
5655
)
57-
from web3.eth import (
58-
AsyncEth,
59-
)
60-
from web3.providers.eth_tester.main import (
61-
AsyncEthereumTesterProvider,
62-
)
6356

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

10661059

1067-
@pytest_asyncio.fixture()
1068-
async def async_w3():
1069-
provider = AsyncEthereumTesterProvider()
1070-
w3 = Web3(provider, modules={'eth': [AsyncEth]},
1071-
middlewares=provider.middlewares)
1072-
w3.eth.default_account = await w3.eth.coinbase
1073-
return w3
1074-
1075-
10761060
@pytest_asyncio.fixture()
10771061
def AsyncMathContract(async_w3, MATH_ABI, MATH_CODE, MATH_RUNTIME):
10781062
contract = AsyncContract.factory(async_w3,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import pytest
2+
3+
from web3._utils.async_transactions import (
4+
fill_transaction_defaults,
5+
)
6+
7+
8+
@pytest.mark.asyncio()
9+
async def test_fill_transaction_defaults_for_all_params(async_w3):
10+
default_transaction = await fill_transaction_defaults(async_w3, {})
11+
12+
block = await async_w3.eth.get_block('latest')
13+
assert default_transaction == {
14+
'chainId': await async_w3.eth.chain_id,
15+
'data': b'',
16+
'gas': await async_w3.eth.estimate_gas({}),
17+
'maxFeePerGas': (
18+
await async_w3.eth.max_priority_fee + (2 * block['baseFeePerGas'])
19+
),
20+
'maxPriorityFeePerGas': await async_w3.eth.max_priority_fee,
21+
'value': 0,
22+
}

web3/_utils/async_transactions.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,63 @@
11
from typing import (
22
TYPE_CHECKING,
3+
Awaitable,
34
Optional,
45
cast,
56
)
67

8+
from eth_utils.toolz import (
9+
curry,
10+
merge,
11+
)
12+
13+
from web3._utils.utility_methods import (
14+
any_in_dict,
15+
)
16+
from web3.constants import (
17+
DYNAMIC_FEE_TXN_PARAMS,
18+
)
719
from web3.types import (
820
BlockIdentifier,
921
TxParams,
22+
Wei,
1023
)
1124

1225
if TYPE_CHECKING:
1326
from web3 import Web3 # noqa: F401
1427
from web3.eth import AsyncEth # noqa: F401
1528

1629

30+
async def _estimate_gas(w3: 'Web3', tx: TxParams) -> Awaitable[int]:
31+
return await w3.eth.estimate_gas(tx) # type: ignore
32+
33+
34+
async def _gas_price(w3: 'Web3', tx: TxParams) -> Awaitable[Optional[Wei]]:
35+
return await w3.eth.generate_gas_price(tx) or w3.eth.gas_price # type: ignore
36+
37+
38+
async def _max_fee_per_gas(w3: 'Web3', tx: TxParams) -> Awaitable[Wei]:
39+
block = await w3.eth.get_block('latest') # type: ignore
40+
return await w3.eth.max_priority_fee + (2 * block['baseFeePerGas']) # type: ignore
41+
42+
43+
async def _max_priority_fee_gas(w3: 'Web3', tx: TxParams) -> Awaitable[Wei]:
44+
return await w3.eth.max_priority_fee # type: ignore
45+
46+
47+
async def _chain_id(w3: 'Web3', tx: TxParams) -> Awaitable[int]:
48+
return await w3.eth.chain_id # type: ignore
49+
50+
TRANSACTION_DEFAULTS = {
51+
'value': 0,
52+
'data': b'',
53+
'gas': _estimate_gas,
54+
'gasPrice': _gas_price,
55+
'maxFeePerGas': _max_fee_per_gas,
56+
'maxPriorityFeePerGas': _max_priority_fee_gas,
57+
'chainId': _chain_id,
58+
}
59+
60+
1761
async def get_block_gas_limit(
1862
web3_eth: "AsyncEth", block_identifier: Optional[BlockIdentifier] = None
1963
) -> int:
@@ -40,3 +84,38 @@ async def get_buffered_gas_estimate(
4084
)
4185

4286
return min(gas_limit, gas_estimate + gas_buffer)
87+
88+
89+
@curry
90+
async def fill_transaction_defaults(w3: "Web3", transaction: TxParams) -> TxParams:
91+
"""
92+
if w3 is None, fill as much as possible while offline
93+
"""
94+
strategy_based_gas_price = await w3.eth.generate_gas_price(transaction) # type: ignore
95+
is_dynamic_fee_transaction = (
96+
not strategy_based_gas_price
97+
and (
98+
'gasPrice' not in transaction # default to dynamic fee transaction
99+
or any_in_dict(DYNAMIC_FEE_TXN_PARAMS, transaction)
100+
)
101+
)
102+
103+
defaults = {}
104+
for key, default_getter in TRANSACTION_DEFAULTS.items():
105+
if key not in transaction:
106+
if (
107+
is_dynamic_fee_transaction and key == 'gasPrice'
108+
or not is_dynamic_fee_transaction and key in DYNAMIC_FEE_TXN_PARAMS
109+
):
110+
# do not set default max fees if legacy txn or gas price if dynamic fee txn
111+
continue
112+
113+
if callable(default_getter):
114+
if w3 is None:
115+
raise ValueError(f"You must specify a '{key}' value in the transaction")
116+
default_val = await default_getter(w3, transaction)
117+
else:
118+
default_val = default_getter
119+
120+
defaults[key] = default_val
121+
return merge(defaults, transaction)

0 commit comments

Comments
 (0)