From 367b5325dec59a72565e3f35218b8eb4c51eabbb Mon Sep 17 00:00:00 2001 From: Felipe Selmo Date: Fri, 12 Nov 2021 14:26:28 -0700 Subject: [PATCH] Peripheral London updates: - update `buildTransaction()` to default to dynamic fee transaction params - add default values for dynamic fee transaction params - update core tests to use dynamic fee transactions by default where appropriate --- docs/web3.eth.account.rst | 1 - newsfragments/2205.misc.rst | 1 + .../test_contract_buildTransaction.py | 39 ++++++++++---- .../test_contract_caller_interface.py | 3 +- tests/core/eth-module/test_accounts.py | 34 +++++++++++- tests/core/filtering/conftest.py | 4 +- .../filtering/test_contract_data_filters.py | 24 +++++---- .../filtering/test_contract_topic_filters.py | 20 ++++--- .../test_valid_transaction_params.py | 53 +++++++++++-------- web3/_utils/transactions.py | 48 ++++++++++------- web3/types.py | 9 ++++ 11 files changed, 165 insertions(+), 71 deletions(-) create mode 100644 newsfragments/2205.misc.rst diff --git a/docs/web3.eth.account.rst b/docs/web3.eth.account.rst index 76182e084b..51996fc83e 100644 --- a/docs/web3.eth.account.rst +++ b/docs/web3.eth.account.rst @@ -313,7 +313,6 @@ To sign a transaction locally that will invoke a smart contract: >>> unicorn_txn {'value': 0, - 'type': '0x2', 'chainId': 1, 'gas': 70000, 'maxFeePerGas': 2000000000, diff --git a/newsfragments/2205.misc.rst b/newsfragments/2205.misc.rst new file mode 100644 index 0000000000..74a7ba540e --- /dev/null +++ b/newsfragments/2205.misc.rst @@ -0,0 +1 @@ +Updates to core tests and contract buildTransaction() method to default to dynamic fee transactions \ No newline at end of file diff --git a/tests/core/contracts/test_contract_buildTransaction.py b/tests/core/contracts/test_contract_buildTransaction.py index 3d3ff9211b..85a8ffe324 100644 --- a/tests/core/contracts/test_contract_buildTransaction.py +++ b/tests/core/contracts/test_contract_buildTransaction.py @@ -54,7 +54,8 @@ def test_build_transaction_not_paying_to_nonpayable_function( 'to': payable_tester_contract.address, 'data': '0xe4cb8f5c', 'value': 0, - 'gasPrice': 10 ** 9, + 'maxFeePerGas': 2750000000, + 'maxPriorityFeePerGas': 10 ** 9, 'chainId': 61, } @@ -75,7 +76,8 @@ def test_build_transaction_with_contract_no_arguments(web3, math_contract, build 'to': math_contract.address, 'data': '0xd09de08a', 'value': 0, - 'gasPrice': 10 ** 9, + 'maxFeePerGas': 2750000000, + 'maxPriorityFeePerGas': 10 ** 9, 'chainId': 61, } @@ -86,7 +88,8 @@ def test_build_transaction_with_contract_fallback_function(web3, fallback_functi 'to': fallback_function_contract.address, 'data': '0x', 'value': 0, - 'gasPrice': 10 ** 9, + 'maxFeePerGas': 2750000000, + 'maxPriorityFeePerGas': 10 ** 9, 'chainId': 61, } @@ -105,7 +108,8 @@ def test_build_transaction_with_contract_class_method( 'to': math_contract.address, 'data': '0xd09de08a', 'value': 0, - 'gasPrice': 10 ** 9, + 'maxFeePerGas': 2750000000, + 'maxPriorityFeePerGas': 10 ** 9, 'chainId': 61, } @@ -119,7 +123,8 @@ def test_build_transaction_with_contract_default_account_is_set( 'to': math_contract.address, 'data': '0xd09de08a', 'value': 0, - 'gasPrice': 10 ** 9, + 'maxFeePerGas': 2750000000, + 'maxPriorityFeePerGas': 10 ** 9, 'chainId': 61, } @@ -162,31 +167,42 @@ def test_build_transaction_with_contract_to_address_supplied_errors(web3, ( {}, (5,), {}, { 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 - 'value': 0, 'gasPrice': 10 ** 9, 'chainId': 61, + 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 1000000000, + 'chainId': 61, }, False ), ( {'gas': 800000}, (5,), {}, { 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 - 'value': 0, 'gasPrice': 10 ** 9, 'chainId': 61, + 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 1000000000, + 'chainId': 61, + }, False + ), + ( # legacy transaction, explicit gasPrice + {'gasPrice': 22 ** 8}, (5,), {}, { + 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 + 'value': 0, 'gasPrice': 22 ** 8, 'chainId': 61, }, False ), ( - {'gasPrice': 10 ** 9}, (5,), {}, { + {'maxFeePerGas': 22 ** 8, 'maxPriorityFeePerGas': 22 ** 8}, (5,), {}, { 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 - 'value': 0, 'gasPrice': 10 ** 9, 'chainId': 61, + 'value': 0, 'maxFeePerGas': 22 ** 8, 'maxPriorityFeePerGas': 22 ** 8, + 'chainId': 61, }, False ), ( {'nonce': 7}, (5,), {}, { 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 - 'value': 0, 'gasPrice': 10 ** 9, 'nonce': 7, 'chainId': 61, + 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 1000000000, + 'nonce': 7, 'chainId': 61, }, True ), ( {'value': 20000}, (5,), {}, { 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 - 'value': 20000, 'gasPrice': 10 ** 9, 'chainId': 61, + 'value': 20000, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 1000000000, + 'chainId': 61, }, False ), ), @@ -194,6 +210,7 @@ def test_build_transaction_with_contract_to_address_supplied_errors(web3, 'Standard', 'Explicit Gas', 'Explicit Gas Price', + 'Explicit Dynamic Fees', 'Explicit Nonce', 'With Value', ] diff --git a/tests/core/contracts/test_contract_caller_interface.py b/tests/core/contracts/test_contract_caller_interface.py index 5a4ee96344..45c4ac7c6a 100644 --- a/tests/core/contracts/test_contract_caller_interface.py +++ b/tests/core/contracts/test_contract_caller_interface.py @@ -43,7 +43,8 @@ def transaction_dict(web3, address): return { 'from': address, 'gas': 210000, - 'gasPrice': web3.toWei(.001, 'ether'), + 'maxFeePerGas': web3.toWei(1, 'gwei'), + 'maxPriorityFeePerGas': web3.toWei(1, 'gwei'), 'value': 12345, } diff --git a/tests/core/eth-module/test_accounts.py b/tests/core/eth-module/test_accounts.py index faf3884202..3c2056eb4a 100644 --- a/tests/core/eth-module/test_accounts.py +++ b/tests/core/eth-module/test_accounts.py @@ -290,6 +290,32 @@ def test_eth_account_sign(acct, 232940010090391255679819602567388136081614408698362277324138554019997613600, 38, ), + ( + { + "gas": 100000, + "gasPrice": 1000000000, + "data": "0x5544", + "nonce": "0x2", + "to": "0x96216849c49358B10257cb55b28eA603c874b05E", + "value": "0x5af3107a4000", + "type": "0x1", + "accessList": ( + { + "address": "0x0000000000000000000000000000000000000001", + "storageKeys": ( + "0x0100000000000000000000000000000000000000000000000000000000000000", + ), + }, + ), + "chainId": 1, + }, + '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318', + HexBytes('0x01f8a70102843b9aca00830186a09496216849c49358b10257cb55b28ea603c874b05e865af3107a4000825544f838f7940000000000000000000000000000000000000001e1a0010000000000000000000000000000000000000000000000000000000000000080a059a827f04246489aca139510f2f82896fd5bd1d34f05228b3fe0c60141db4246a07a28a925eae2e3ee173cf691de2737cbff9160fc527ca4305740a31f154758c5'), # noqa: 501 + HexBytes('0x5daf67cf0dd95f338e98b7b8ee7635f3667b7127be8f42011536c239f8ed4f50'), + 40552949476267726331782755436085615371483644858950895384477411974599262552646, + 55254008827136682571969715431199989606887296450225146219777298167488873715909, + 0, + ), ( { "gas": 100000, @@ -337,7 +363,13 @@ def test_eth_account_sign(acct, 0, ) ), - ids=['web3js_example', '31byte_r_and_s', 'dynamic_fee_txn_example', 'dynamic_fee_txn_hex_fees'], + ids=[ + 'web3js_example', + '31byte_r_and_s', + 'access_list_txn', + 'dynamic_fee_txn', + 'dynamic_fee_txn_hex_fees', + ], ) def test_eth_account_sign_transaction(acct, txn, private_key, expected_raw_tx, tx_hash, r, s, v): signed = acct.sign_transaction(txn, private_key) diff --git a/tests/core/filtering/conftest.py b/tests/core/filtering/conftest.py index 1e66b25998..fd161cf51a 100644 --- a/tests/core/filtering/conftest.py +++ b/tests/core/filtering/conftest.py @@ -76,7 +76,9 @@ def emitter(web3, Emitter, wait_for_transaction, wait_for_block, address_convers deploy_txn_hash = Emitter.constructor().transact({ 'from': web3.eth.coinbase, 'gas': 1000000, - 'gasPrice': 10 ** 9}) + 'maxFeePerGas': 10 ** 9, + 'maxPriorityFeePerGas': 10 ** 9, + }) deploy_receipt = wait_for_transaction(web3, deploy_txn_hash) contract_address = address_conversion_func(deploy_receipt['contractAddress']) diff --git a/tests/core/filtering/test_contract_data_filters.py b/tests/core/filtering/test_contract_data_filters.py index aef0075c77..f8b4452480 100644 --- a/tests/core/filtering/test_contract_data_filters.py +++ b/tests/core/filtering/test_contract_data_filters.py @@ -70,7 +70,9 @@ def emitter(web3, Emitter, wait_for_transaction, wait_for_block, address_convers deploy_txn_hash = Emitter.constructor().transact({ 'from': web3.eth.coinbase, 'gas': 1000000, - 'gasPrice': 10 ** 9}) + 'maxFeePerGas': 10 ** 9, + 'maxPriorityFeePerGas': 10 ** 9, + }) deploy_receipt = wait_for_transaction(web3, deploy_txn_hash) contract_address = address_conversion_func(deploy_receipt['contractAddress']) @@ -153,10 +155,10 @@ def test_data_filters_with_dynamic_arguments( txn_hashes = [ emitter.functions.logDynamicArgs( arg0=vals['matching'], arg1=vals['matching']).transact( - {'gasPrice': 10 ** 9, 'gas': 400000}), + {'maxFeePerGas': 10 ** 9, 'maxPriorityFeePerGas': 10 ** 9, 'gas': 400000}), emitter.functions.logDynamicArgs( arg0=vals['non_matching'][0], arg1=vals['non_matching'][0]).transact( - {'gasPrice': 10 ** 9, 'gas': 400000}), + {'maxFeePerGas': 10 ** 9, 'maxPriorityFeePerGas': 10 ** 9, 'gas': 400000}), ] for txn_hash in txn_hashes: @@ -200,13 +202,17 @@ def test_data_filters_with_fixed_arguments( arg0=vals['matching'][0], arg1=vals['matching'][1], arg2=vals['matching'][2], - arg3=vals['matching'][3]).transact({'gasPrice': 10 ** 9, 'gas': 100000})) + arg3=vals['matching'][3]).transact({ + 'maxFeePerGas': 10 ** 9, 'maxPriorityFeePerGas': 10 ** 9, 'gas': 100000 + })) txn_hashes.append(emitter.functions.logQuadruple( which=5, arg0=vals['non_matching'][0], arg1=vals['non_matching'][1], arg2=vals['non_matching'][2], - arg3=vals['non_matching'][3]).transact({'gasPrice': 10 ** 9, 'gas': 100000})) + arg3=vals['non_matching'][3]).transact({ + 'maxFeePerGas': 10 ** 9, 'maxPriorityFeePerGas': 10 ** 9, 'gas': 100000 + })) for txn_hash in txn_hashes: wait_for_transaction(web3, txn_hash) @@ -239,16 +245,16 @@ def test_data_filters_with_list_arguments( txn_hashes = [] txn_hashes.append(emitter.functions.logListArgs( arg0=matching, - arg1=matching).transact({'gasPrice': 10 ** 9})) + arg1=matching).transact({'maxFeePerGas': 10 ** 9, 'maxPriorityFeePerGas': 10 ** 9})) txn_hashes.append(emitter.functions.logListArgs( arg0=non_matching, - arg1=non_matching).transact({'gasPrice': 10 ** 9})) + arg1=non_matching).transact({'maxFeePerGas': 10 ** 9, 'maxPriorityFeePerGas': 10 ** 9})) txn_hashes.append(emitter.functions.logListArgs( arg0=non_matching, - arg1=matching).transact({'gasPrice': 10 ** 9})) + arg1=matching).transact({'maxFeePerGas': 10 ** 9, 'maxPriorityFeePerGas': 10 ** 9})) txn_hashes.append(emitter.functions.logListArgs( arg0=matching, - arg1=non_matching).transact({'gasPrice': 10 ** 9})) + arg1=non_matching).transact({'maxFeePerGas': 10 ** 9, 'maxPriorityFeePerGas': 10 ** 9})) for txn_hash in txn_hashes: wait_for_transaction(web3, txn_hash) diff --git a/tests/core/filtering/test_contract_topic_filters.py b/tests/core/filtering/test_contract_topic_filters.py index 4b761be075..27e4589c6c 100644 --- a/tests/core/filtering/test_contract_topic_filters.py +++ b/tests/core/filtering/test_contract_topic_filters.py @@ -81,10 +81,14 @@ def test_topic_filters_with_dynamic_arguments( txn_hashes = [ emitter.functions.logDynamicArgs( arg0=vals['matching'], - arg1=vals['matching']).transact({'gasPrice': 10 ** 9, 'gas': 60000}), + arg1=vals['matching']).transact({ + 'maxFeePerGas': 10 ** 9, 'maxPriorityFeePerGas': 10 ** 9, 'gas': 60000 + }), emitter.functions.logDynamicArgs( arg0=vals['non_matching'][0], - arg1=vals['non_matching'][0]).transact({'gasPrice': 10 ** 9, 'gas': 60000}) + arg1=vals['non_matching'][0]).transact({ + 'maxFeePerGas': 10 ** 9, 'maxPriorityFeePerGas': 10 ** 9, 'gas': 60000 + }) ] for txn_hash in txn_hashes: @@ -130,13 +134,17 @@ def test_topic_filters_with_fixed_arguments( arg0=vals['matching'][0], arg1=vals['matching'][1], arg2=vals['matching'][2], - arg3=vals['matching'][3]).transact({'gasPrice': 10 ** 9, 'gas': 60000})) + arg3=vals['matching'][3]).transact({ + 'maxFeePerGas': 10 ** 9, 'maxPriorityFeePerGas': 10 ** 9, 'gas': 60000 + })) txn_hashes.append(emitter.functions.logQuadruple( which=11, arg0=vals['non_matching'][0], arg1=vals['non_matching'][1], arg2=vals['non_matching'][2], - arg3=vals['non_matching'][3]).transact({'gasPrice': 10 ** 9, 'gas': 60000})) + arg3=vals['non_matching'][3]).transact({ + 'maxFeePerGas': 10 ** 9, 'maxPriorityFeePerGas': 10 ** 9, 'gas': 60000 + })) for txn_hash in txn_hashes: wait_for_transaction(web3, txn_hash) @@ -167,10 +175,10 @@ def test_topic_filters_with_list_arguments( txn_hashes = [] txn_hashes.append(emitter.functions.logListArgs( arg0=matching, - arg1=matching).transact({'gasPrice': 10 ** 9})) + arg1=matching).transact({'maxFeePerGas': 10 ** 9, 'maxPriorityFeePerGas': 10 ** 9})) txn_hashes.append(emitter.functions.logListArgs( arg0=non_matching, - arg1=non_matching).transact({'gasPrice': 10 ** 9})) + arg1=non_matching).transact({'maxFeePerGas': 10 ** 9, 'maxPriorityFeePerGas': 10 ** 9})) for txn_hash in txn_hashes: wait_for_transaction(web3, txn_hash) diff --git a/tests/core/utilities/test_valid_transaction_params.py b/tests/core/utilities/test_valid_transaction_params.py index b7898c2746..4424efa00b 100644 --- a/tests/core/utilities/test_valid_transaction_params.py +++ b/tests/core/utilities/test_valid_transaction_params.py @@ -17,6 +17,19 @@ def test_assert_valid_transaction_params_all_params(): 'gasPrice': 5000000, 'maxFeePerGas': 2000000000, 'maxPriorityFeePerGas': 1000000000, + 'accessList': ( + { + 'address': '0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae', + 'storageKeys': ( + '0x0000000000000000000000000000000000000000000000000000000000000003', + '0x0000000000000000000000000000000000000000000000000000000000000007', + ) + }, + { + 'address': '0xbb9bc244d798123fde783fcc1c72d3bb8c189413', + 'storageKeys': () + }, + ), 'value': 1, 'data': '0x0', 'nonce': 2, @@ -54,6 +67,19 @@ def test_assert_valid_transaction_params_invalid_param(): 'value': 3, 'nonce': 2, 'chainId': 1, + 'accessList': ( + { + 'address': '0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae', + 'storageKeys': ( + '0x0000000000000000000000000000000000000000000000000000000000000003', + '0x0000000000000000000000000000000000000000000000000000000000000007', + ) + }, + { + 'address': '0xbb9bc244d798123fde783fcc1c72d3bb8c189413', + 'storageKeys': () + }, + ), } @@ -117,28 +143,9 @@ def test_fill_transaction_defaults_for_all_params(web3): 'chainId': web3.eth.chain_id, 'data': b'', 'gas': web3.eth.estimate_gas({}), - 'gasPrice': web3.eth.gas_price, + 'maxFeePerGas': ( + web3.eth.max_priority_fee + (2 * web3.eth.get_block('latest')['baseFeePerGas']) + ), + 'maxPriorityFeePerGas': web3.eth.max_priority_fee, 'value': 0, } - - -def test_fill_transaction_defaults_sets_type_with_dynamic_fee_txn_params_and_no_gas_price(web3): - dynamic_fee_transaction = { - 'chainId': 1, - 'data': b'123', - 'gas': 21000, - 'maxFeePerGas': 2000000000, - 'maxPriorityFeePerGas': 1000000000, - 'value': 2, - } - dynamic_fee_transaction_type_added = fill_transaction_defaults(web3, dynamic_fee_transaction) - - assert dynamic_fee_transaction_type_added == { - 'chainId': 1, - 'data': b'123', - 'gas': 21000, - 'maxFeePerGas': 2000000000, - 'maxPriorityFeePerGas': 1000000000, - 'type': '0x2', # type is added and defaults to '0x2' when dynamic fee txn params present - 'value': 2, - } diff --git a/web3/_utils/transactions.py b/web3/_utils/transactions.py index 5c2671c089..d63ea51d42 100644 --- a/web3/_utils/transactions.py +++ b/web3/_utils/transactions.py @@ -44,13 +44,14 @@ ) TX_PARAM_LITERALS = Literal['type', 'from', 'to', 'gas', 'maxFeePerGas', 'maxPriorityFeePerGas', - 'gasPrice', 'value', 'data', 'nonce', 'chainId'] + 'gasPrice', 'value', 'data', 'nonce', 'chainId', 'accessList'] VALID_TRANSACTION_PARAMS: List[TX_PARAM_LITERALS] = [ 'type', 'from', 'to', 'gas', + 'accessList', 'maxFeePerGas', 'maxPriorityFeePerGas', 'gasPrice', @@ -65,6 +66,11 @@ 'data': b'', 'gas': lambda web3, tx: web3.eth.estimate_gas(tx), 'gasPrice': lambda web3, tx: web3.eth.generate_gas_price(tx) or web3.eth.gas_price, + 'maxFeePerGas': ( + lambda web3, tx: + web3.eth.max_priority_fee + (2 * web3.eth.get_block('latest')['baseFeePerGas']) + ), + 'maxPriorityFeePerGas': lambda web3, tx: web3.eth.max_priority_fee, 'chainId': lambda web3, tx: web3.eth.chain_id, } @@ -90,27 +96,33 @@ def fill_transaction_defaults(web3: "Web3", transaction: TxParams) -> TxParams: """ if web3 is None, fill as much as possible while offline """ + strategy_based_gas_price = web3.eth.generate_gas_price(transaction) + 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 key == 'gasPrice' and any_in_dict(DYNAMIC_FEE_TXN_PARAMS, transaction): - # if dynamic fee txn params present, do not set a default 'gasPrice' if missing + 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 web3 is not None: - default_val = default_getter(web3, transaction) - else: - raise ValueError("You must specify %s in the transaction" % key) - + if web3 is None: + raise ValueError("You must specify a '%s' value in the transaction" % key) + default_val = default_getter(web3, transaction) else: default_val = default_getter - defaults[key] = default_val - - if 'type' not in transaction and any_in_dict(DYNAMIC_FEE_TXN_PARAMS, transaction): - # default transaction type to '2' if dynamic fee txn params are present - defaults['type'] = '0x2' + defaults[key] = default_val return merge(defaults, transaction) @@ -172,11 +184,11 @@ def extract_valid_transaction_params(transaction_params: TxData) -> TxParams: for key in VALID_TRANSACTION_PARAMS if key in transaction_params }) - # There is always a gasPrice now on eth_getTransaction call, even for dynamic fee transactions. - # For dynamic fee transactions, we need to pull the gasPrice value back out of the extracted - # params if it is equal to the expected value (maxFeePerGas). If we don't, the modified - # transaction will include a gasPrice as well as dynamic fee values in the - # eth_sendTransaction call and cause a conflict. + # There is always a gasPrice now on eth_getTransaction call for pending transactions, including + # dynamic fee transactions. For dynamic fee transactions, we need to pull the gasPrice value + # back out of the extracted params if it is equal to the expected value (maxFeePerGas). If + # we don't, the modified transaction will include a gasPrice as well as dynamic fee values in + # the eth_sendTransaction call and cause a conflict. if all_in_dict(DYNAMIC_FEE_TXN_PARAMS, extracted_params): if extracted_params['gasPrice'] == extracted_params['maxFeePerGas']: extracted_params.pop('gasPrice') diff --git a/web3/types.py b/web3/types.py index c5470dcd84..f475fc14d3 100644 --- a/web3/types.py +++ b/web3/types.py @@ -60,6 +60,14 @@ Formatters = Dict[RPCEndpoint, Callable[..., Any]] +class AccessListEntry(TypedDict): + address: HexStr + storageKeys: Sequence[HexStr] + + +AccessList = NewType("AccessList", Sequence[AccessListEntry]) + + # todo: move these to eth_typing once web3 is type hinted class ABIEventParams(TypedDict, total=False): indexed: bool @@ -167,6 +175,7 @@ class LogReceipt(TypedDict): # syntax b/c "from" keyword not allowed w/ class construction TxData = TypedDict("TxData", { + "accessList": AccessList, "blockHash": HexBytes, "blockNumber": BlockNumber, "chainId": int,