diff --git a/tests/core/contracts/test_contract_buildTransaction.py b/tests/core/contracts/test_contract_buildTransaction.py index 637c4ddb84..85a8ffe324 100644 --- a/tests/core/contracts/test_contract_buildTransaction.py +++ b/tests/core/contracts/test_contract_buildTransaction.py @@ -54,8 +54,8 @@ def test_build_transaction_not_paying_to_nonpayable_function( 'to': payable_tester_contract.address, 'data': '0xe4cb8f5c', 'value': 0, - 'maxFeePerGas': 3750000000, - 'maxPriorityFeePerGas': 2 * (10 ** 9), + 'maxFeePerGas': 2750000000, + 'maxPriorityFeePerGas': 10 ** 9, 'chainId': 61, } @@ -76,8 +76,8 @@ def test_build_transaction_with_contract_no_arguments(web3, math_contract, build 'to': math_contract.address, 'data': '0xd09de08a', 'value': 0, - 'maxFeePerGas': 3750000000, - 'maxPriorityFeePerGas': 2 * (10 ** 9), + 'maxFeePerGas': 2750000000, + 'maxPriorityFeePerGas': 10 ** 9, 'chainId': 61, } @@ -88,8 +88,8 @@ def test_build_transaction_with_contract_fallback_function(web3, fallback_functi 'to': fallback_function_contract.address, 'data': '0x', 'value': 0, - 'maxFeePerGas': 3750000000, - 'maxPriorityFeePerGas': 2 * (10 ** 9), + 'maxFeePerGas': 2750000000, + 'maxPriorityFeePerGas': 10 ** 9, 'chainId': 61, } @@ -108,8 +108,8 @@ def test_build_transaction_with_contract_class_method( 'to': math_contract.address, 'data': '0xd09de08a', 'value': 0, - 'maxFeePerGas': 3750000000, - 'maxPriorityFeePerGas': 2 * (10 ** 9), + 'maxFeePerGas': 2750000000, + 'maxPriorityFeePerGas': 10 ** 9, 'chainId': 61, } @@ -123,8 +123,8 @@ def test_build_transaction_with_contract_default_account_is_set( 'to': math_contract.address, 'data': '0xd09de08a', 'value': 0, - 'maxFeePerGas': 3750000000, - 'maxPriorityFeePerGas': 2 * (10 ** 9), + 'maxFeePerGas': 2750000000, + 'maxPriorityFeePerGas': 10 ** 9, 'chainId': 61, } @@ -167,14 +167,14 @@ def test_build_transaction_with_contract_to_address_supplied_errors(web3, ( {}, (5,), {}, { 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 - 'value': 0, 'maxFeePerGas': 3750000000, 'maxPriorityFeePerGas': 2 * (10 ** 9), + 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 1000000000, 'chainId': 61, }, False ), ( {'gas': 800000}, (5,), {}, { 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 - 'value': 0, 'maxFeePerGas': 3750000000, 'maxPriorityFeePerGas': 2 * (10 ** 9), + 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 1000000000, 'chainId': 61, }, False ), @@ -194,14 +194,14 @@ def test_build_transaction_with_contract_to_address_supplied_errors(web3, ( {'nonce': 7}, (5,), {}, { 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 - 'value': 0, 'maxFeePerGas': 3750000000, 'maxPriorityFeePerGas': 2 * (10 ** 9), + 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 1000000000, 'nonce': 7, 'chainId': 61, }, True ), ( {'value': 20000}, (5,), {}, { 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 - 'value': 20000, 'maxFeePerGas': 3750000000, 'maxPriorityFeePerGas': 2 * (10 ** 9), + 'value': 20000, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 1000000000, 'chainId': 61, }, False ), diff --git a/tests/core/utilities/test_fee_utils.py b/tests/core/utilities/test_fee_utils.py new file mode 100644 index 0000000000..8bffb05714 --- /dev/null +++ b/tests/core/utilities/test_fee_utils.py @@ -0,0 +1,73 @@ +import pytest + +from eth_utils import ( + is_integer, +) + +from web3.middleware import ( + construct_error_generator_middleware, + construct_result_generator_middleware, +) +from web3.types import ( + RPCEndpoint, +) + + +@pytest.mark.parametrize( + 'fee_history_result,expected_max_prio_calc', + ( + ( + { + 'reward': [[10 ** 20], [10 ** 20], [10 ** 20], [10 ** 20]], + }, + 15 * (10 ** 8), + ), + ( + { + 'reward': [[10 ** 2], [10 ** 2], [10 ** 2], [10 ** 2], [10 ** 2]], + }, + 10 ** 9, + ), + ( + { + 'reward': [[0], [0], [0], [0], [0]], + }, + 10 ** 9, + ), + ( + { + 'reward': [[1223344455], [1111111111], [1222777777], [0], [1000222111], [0], [0]], + }, + round(sum([1223344455, 1111111111, 1222777777, 1000222111]) / 4), + ), + ), + ids=[ + 'test-max', 'test-min', 'test-min-all-zero-fees', 'test-non-zero-average' + ] +) +# Test fee_utils indirectly by mocking eth_feeHistory results and checking against expected output +def test_fee_utils_indirectly( + web3, fee_history_result, expected_max_prio_calc +) -> None: + fail_max_prio_middleware = construct_error_generator_middleware( + {RPCEndpoint("eth_maxPriorityFeePerGas"): lambda *_: ''} + ) + fee_history_result_middleware = construct_result_generator_middleware( + {RPCEndpoint('eth_feeHistory'): lambda *_: fee_history_result} + ) + + web3.middleware_onion.add(fail_max_prio_middleware, 'fail_max_prio') + web3.middleware_onion.inject(fee_history_result_middleware, 'fee_history_result', layer=0) + + with pytest.warns( + UserWarning, + match="There was an issue with the method eth_maxPriorityFeePerGas. Calculating using " + "eth_feeHistory." + ): + max_priority_fee = web3.eth.max_priority_fee + assert is_integer(max_priority_fee) + assert max_priority_fee == expected_max_prio_calc + + # clean up + web3.middleware_onion.remove('fail_max_prio') + web3.middleware_onion.remove('fee_history_result') diff --git a/tests/integration/test_ethereum_tester.py b/tests/integration/test_ethereum_tester.py index 42dc4ab98c..5d9297e723 100644 --- a/tests/integration/test_ethereum_tester.py +++ b/tests/integration/test_ethereum_tester.py @@ -239,6 +239,10 @@ def func_wrapper(self, eth_tester, *args, **kwargs): class TestEthereumTesterEthModule(EthModuleTest): + test_eth_max_priority_fee_with_fee_history_calculation = not_implemented( + EthModuleTest.test_eth_max_priority_fee_with_fee_history_calculation, + ValueError + ) test_eth_sign = not_implemented(EthModuleTest.test_eth_sign, ValueError) test_eth_sign_ens_names = not_implemented( EthModuleTest.test_eth_sign_ens_names, ValueError diff --git a/web3/_utils/module_testing/eth_module.py b/web3/_utils/module_testing/eth_module.py index 80cd38ef5a..7097d71f03 100644 --- a/web3/_utils/module_testing/eth_module.py +++ b/web3/_utils/module_testing/eth_module.py @@ -54,11 +54,16 @@ TransactionNotFound, TransactionTypeMismatch, ) +from web3.middleware.fixture import ( + async_construct_error_generator_middleware, + construct_error_generator_middleware, +) from web3.types import ( # noqa: F401 BlockData, FilterParams, LogReceipt, Nonce, + RPCEndpoint, SyncStatus, TxParams, Wei, @@ -423,6 +428,25 @@ async def test_eth_max_priority_fee(self, async_w3: "Web3") -> None: max_priority_fee = await async_w3.eth.max_priority_fee # type: ignore assert is_integer(max_priority_fee) + @pytest.mark.asyncio + async def test_eth_max_priority_fee_with_fee_history_calculation( + self, async_w3: "Web3" + ) -> None: + fail_max_prio_middleware = await async_construct_error_generator_middleware( + {RPCEndpoint("eth_maxPriorityFeePerGas"): lambda *_: ''} + ) + async_w3.middleware_onion.add(fail_max_prio_middleware, name='fail_max_prio_middleware') + + with pytest.warns( + UserWarning, + match="There was an issue with the method eth_maxPriorityFeePerGas. Calculating using " + "eth_feeHistory." + ): + max_priority_fee = await async_w3.eth.max_priority_fee # type: ignore + assert is_integer(max_priority_fee) + + async_w3.middleware_onion.remove('fail_max_prio_middleware') # clean up + @pytest.mark.asyncio async def test_eth_getBlockByHash( self, async_w3: "Web3", empty_block: BlockData @@ -1136,6 +1160,22 @@ def test_eth_max_priority_fee(self, web3: "Web3") -> None: max_priority_fee = web3.eth.max_priority_fee assert is_integer(max_priority_fee) + def test_eth_max_priority_fee_with_fee_history_calculation(self, web3: "Web3") -> None: + fail_max_prio_middleware = construct_error_generator_middleware( + {RPCEndpoint("eth_maxPriorityFeePerGas"): lambda *_: ''} + ) + web3.middleware_onion.add(fail_max_prio_middleware, name='fail_max_prio_middleware') + + with pytest.warns( + UserWarning, + match="There was an issue with the method eth_maxPriorityFeePerGas. Calculating using " + "eth_feeHistory." + ): + max_priority_fee = web3.eth.max_priority_fee + assert is_integer(max_priority_fee) + + web3.middleware_onion.remove('fail_max_prio_middleware') # clean up + def test_eth_accounts(self, web3: "Web3") -> None: accounts = web3.eth.accounts assert is_list_like(accounts) diff --git a/web3/middleware/fixture.py b/web3/middleware/fixture.py index b54d0902a8..1565ab50a7 100644 --- a/web3/middleware/fixture.py +++ b/web3/middleware/fixture.py @@ -2,6 +2,7 @@ TYPE_CHECKING, Any, Callable, + Coroutine, Dict, ) @@ -21,7 +22,7 @@ def construct_fixture_middleware(fixtures: Dict[RPCEndpoint, Any]) -> Middleware which is found in the provided fixtures. """ def fixture_middleware( - make_request: Callable[[RPCEndpoint, Any], Any], web3: "Web3" + make_request: Callable[[RPCEndpoint, Any], Any], _: "Web3" ) -> Callable[[RPCEndpoint, Any], RPCResponse]: def middleware(method: RPCEndpoint, params: Any) -> RPCResponse: if method in fixtures: @@ -43,7 +44,7 @@ def construct_result_generator_middleware( functions with the signature `fn(method, params)`. """ def result_generator_middleware( - make_request: Callable[[RPCEndpoint, Any], Any], web3: "Web3" + make_request: Callable[[RPCEndpoint, Any], Any], _: "Web3" ) -> Callable[[RPCEndpoint, Any], RPCResponse]: def middleware(method: RPCEndpoint, params: Any) -> RPCResponse: if method in result_generators: @@ -65,7 +66,7 @@ def construct_error_generator_middleware( functions with the signature `fn(method, params)`. """ def error_generator_middleware( - make_request: Callable[[RPCEndpoint, Any], Any], web3: "Web3" + make_request: Callable[[RPCEndpoint, Any], Any], _: "Web3" ) -> Callable[[RPCEndpoint, Any], RPCResponse]: def middleware(method: RPCEndpoint, params: Any) -> RPCResponse: if method in error_generators: @@ -75,3 +76,25 @@ def middleware(method: RPCEndpoint, params: Any) -> RPCResponse: return make_request(method, params) return middleware return error_generator_middleware + + +async def async_construct_error_generator_middleware( + error_generators: Dict[RPCEndpoint, Any] +) -> Middleware: + """ + Constructs a middleware which intercepts requests for any method found in + the provided mapping of endpoints to generator functions, returning + whatever error message the generator function returns. Callbacks must be + functions with the signature `fn(method, params)`. + """ + async def error_generator_middleware( + make_request: Callable[[RPCEndpoint, Any], Any], _: "Web3" + ) -> Callable[[RPCEndpoint, Any], Coroutine[Any, Any, RPCResponse]]: + async def middleware(method: RPCEndpoint, params: Any) -> RPCResponse: + if method in error_generators: + error_msg = error_generators[method](method, params) + return {'error': error_msg} + else: + return await make_request(method, params) + return middleware + return error_generator_middleware