Skip to content

Commit c941d85

Browse files
committed
base default maxFeePerGas value off of maxPriorityFeePerGas instead of a separate max_priority_fee rpc call
1 parent bfe0706 commit c941d85

File tree

5 files changed

+145
-19
lines changed

5 files changed

+145
-19
lines changed

newsfragments/3052.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Base default ``maxFeePerGas`` calculation off of existing ``maxPriorityFeePerGas`` key instead of separate RPC call

tests/core/utilities/test_async_transaction.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import pytest
2+
from unittest.mock import (
3+
AsyncMock,
4+
patch,
5+
)
26

37
from eth_typing import (
48
BlockNumber,
@@ -15,6 +19,9 @@
1519
from web3.constants import (
1620
DYNAMIC_FEE_TXN_PARAMS,
1721
)
22+
from web3.eth import (
23+
AsyncEth,
24+
)
1825

1926
SIMPLE_CURRENT_TRANSACTION = {
2027
"blockHash": None,
@@ -79,6 +86,61 @@ async def test_async_fill_transaction_defaults_nondynamic_tranaction_fee(async_w
7986
assert none_in_dict(DYNAMIC_FEE_TXN_PARAMS, default_transaction)
8087

8188

89+
@pytest.mark.asyncio
90+
async def test_async_default_max_fee_per_gas_uses_max_priority_fee_if_exists_in_tx(
91+
async_w3,
92+
):
93+
fixed_base_fee = 500
94+
95+
async def get_block_func(block_number):
96+
return {"baseFeePerGas": fixed_base_fee}
97+
98+
get_block_mock = AsyncMock(side_effect=get_block_func)
99+
100+
with patch.object(async_w3.eth, "get_block", side_effect=get_block_mock):
101+
default_transaction = await async_fill_transaction_defaults(
102+
async_w3, {"maxPriorityFeePerGas": 100}
103+
)
104+
105+
assert default_transaction == {
106+
"chainId": await async_w3.eth.chain_id,
107+
"data": b"",
108+
"gas": await async_w3.eth.estimate_gas({}),
109+
"value": 0,
110+
"maxPriorityFeePerGas": 100,
111+
"maxFeePerGas": 1100,
112+
}
113+
114+
115+
@pytest.mark.asyncio
116+
async def test_async_default_max_fee_per_gas_uses_default_mpfee_per_gas_if_not_in_tx(
117+
async_w3,
118+
):
119+
fixed_base_fee = 500
120+
121+
async def get_block_func(value):
122+
return {"baseFeePerGas": fixed_base_fee}
123+
124+
get_block_mock = AsyncMock(side_effect=get_block_func)
125+
126+
@property
127+
async def max_priority_fee_mock(arg):
128+
return 45
129+
130+
with patch.object(async_w3.eth, "get_block", side_effect=get_block_mock):
131+
with patch.object(AsyncEth, "max_priority_fee", new=max_priority_fee_mock):
132+
default_transaction = await async_fill_transaction_defaults(async_w3, {})
133+
134+
assert default_transaction == {
135+
"chainId": await async_w3.eth.chain_id,
136+
"data": b"",
137+
"gas": await async_w3.eth.estimate_gas({}),
138+
"value": 0,
139+
"maxPriorityFeePerGas": 45,
140+
"maxFeePerGas": 1045,
141+
}
142+
143+
82144
@pytest.mark.asyncio
83145
async def test_async_fill_transaction_defaults_for_zero_gas_price(async_w3):
84146
def gas_price_strategy(_w3, tx):

tests/core/utilities/test_valid_transaction_params.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import pytest
2+
from unittest.mock import (
3+
patch,
4+
)
25

36
from web3._utils.transactions import (
47
assert_valid_transaction_params,
58
extract_valid_transaction_params,
69
fill_transaction_defaults,
710
)
11+
from web3.eth import (
12+
Eth,
13+
)
814
from web3.exceptions import (
915
Web3AttributeError,
1016
Web3ValueError,
@@ -161,6 +167,47 @@ def test_fill_transaction_defaults_for_all_params(w3):
161167
}
162168

163169

170+
def test_default_max_fee_per_gas_uses_max_priority_fee_if_exists_in_tx(w3):
171+
fixed_base_fee = 500
172+
173+
def get_block_mock(block_number):
174+
return {"baseFeePerGas": fixed_base_fee}
175+
176+
with patch.object(w3.eth, "get_block", side_effect=get_block_mock):
177+
default_transaction = fill_transaction_defaults(
178+
w3, {"maxPriorityFeePerGas": 100}
179+
)
180+
181+
assert default_transaction == {
182+
"chainId": w3.eth.chain_id,
183+
"data": b"",
184+
"gas": w3.eth.estimate_gas({}),
185+
"value": 0,
186+
"maxPriorityFeePerGas": 100,
187+
"maxFeePerGas": 1100,
188+
}
189+
190+
191+
def test_default_max_fee_per_gas_based_on_default_max_prio_fee_per_gas_if_not_in_tx(w3):
192+
fixed_base_fee = 500
193+
194+
def get_block_mock(value):
195+
return {"baseFeePerGas": fixed_base_fee}
196+
197+
with patch.object(w3.eth, "get_block", side_effect=get_block_mock):
198+
with patch.object(Eth, "max_priority_fee", 45):
199+
default_transaction = fill_transaction_defaults(w3, {})
200+
201+
assert default_transaction == {
202+
"chainId": w3.eth.chain_id,
203+
"data": b"",
204+
"gas": w3.eth.estimate_gas({}),
205+
"value": 0,
206+
"maxPriorityFeePerGas": 45,
207+
"maxFeePerGas": 1045,
208+
}
209+
210+
164211
def test_fill_transaction_defaults_for_zero_gas_price(w3):
165212
def gas_price_strategy(_w3, tx):
166213
return 0

web3/_utils/async_transactions.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from typing import (
22
TYPE_CHECKING,
3+
Dict,
34
Optional,
5+
Union,
46
cast,
57
)
68

@@ -42,31 +44,42 @@
4244
)
4345

4446

45-
async def _estimate_gas(async_w3: "AsyncWeb3", tx: TxParams) -> int:
47+
# unused vars present in these funcs because they all need to have the same signature
48+
async def _estimate_gas(
49+
async_w3: "AsyncWeb3", tx: TxParams, _defaults: Dict[str, Union[bytes, int]]
50+
) -> int:
4651
return await async_w3.eth.estimate_gas(tx)
4752

4853

49-
async def _max_fee_per_gas(async_w3: "AsyncWeb3", _tx: TxParams) -> Wei:
54+
async def _max_fee_per_gas(
55+
async_w3: "AsyncWeb3", tx: TxParams, defaults: Dict[str, Union[bytes, int]]
56+
) -> Wei:
5057
block = await async_w3.eth.get_block("latest")
51-
max_priority_fee = await async_w3.eth.max_priority_fee
52-
return Wei(max_priority_fee + (2 * block["baseFeePerGas"]))
58+
max_priority_fee = tx.get(
59+
"maxPriorityFeePerGas", defaults.get("maxPriorityFeePerGas")
60+
)
61+
return Wei(int(max_priority_fee) + (2 * int(block["baseFeePerGas"])))
5362

5463

55-
async def _max_priority_fee_gas(async_w3: "AsyncWeb3", _tx: TxParams) -> Wei:
64+
async def _max_priority_fee_gas(
65+
async_w3: "AsyncWeb3", _tx: TxParams, _defaults: Dict[str, Union[bytes, int]]
66+
) -> Wei:
5667
return await async_w3.eth.max_priority_fee
5768

5869

59-
async def _chain_id(async_w3: "AsyncWeb3", _tx: TxParams) -> int:
70+
async def _chain_id(
71+
async_w3: "AsyncWeb3", _tx: TxParams, _defaults: Dict[str, Union[bytes, int]]
72+
) -> int:
6073
return await async_w3.eth.chain_id
6174

6275

6376
TRANSACTION_DEFAULTS = {
6477
"value": 0,
6578
"data": b"",
6679
"gas": _estimate_gas,
67-
"gasPrice": lambda async_w3, tx: async_w3.eth.generate_gas_price(tx),
68-
"maxFeePerGas": _max_fee_per_gas,
80+
"gasPrice": lambda async_w3, tx, _defaults: async_w3.eth.generate_gas_price(tx),
6981
"maxPriorityFeePerGas": _max_priority_fee_gas,
82+
"maxFeePerGas": _max_fee_per_gas,
7083
"chainId": _chain_id,
7184
}
7285

@@ -122,7 +135,7 @@ async def async_fill_transaction_defaults(
122135
or any_in_dict(DYNAMIC_FEE_TXN_PARAMS, transaction)
123136
)
124137

125-
defaults = {}
138+
defaults: Dict[str, Union[bytes, int]] = {}
126139
for key, default_getter in TRANSACTION_DEFAULTS.items():
127140
if key not in transaction:
128141
if (
@@ -143,9 +156,9 @@ async def async_fill_transaction_defaults(
143156
if key == "gasPrice":
144157
# `generate_gas_price()` is on the `BaseEth` class and does not
145158
# need to be awaited
146-
default_val = default_getter(async_w3, transaction)
159+
default_val = default_getter(async_w3, transaction, defaults)
147160
else:
148-
default_val = await default_getter(async_w3, transaction)
161+
default_val = await default_getter(async_w3, transaction, defaults)
149162
else:
150163
default_val = default_getter
151164

web3/_utils/transactions.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import math
22
from typing import (
33
TYPE_CHECKING,
4+
Dict,
45
List,
56
Literal,
67
Optional,
@@ -75,14 +76,16 @@
7576
TRANSACTION_DEFAULTS = {
7677
"value": 0,
7778
"data": b"",
78-
"gas": lambda w3, tx: w3.eth.estimate_gas(tx),
79-
"gasPrice": lambda w3, tx: w3.eth.generate_gas_price(tx),
79+
"gas": lambda w3, tx, _defaults: w3.eth.estimate_gas(tx),
80+
"gasPrice": lambda w3, tx, _defaults: w3.eth.generate_gas_price(tx),
81+
"maxPriorityFeePerGas": lambda w3, _tx, _defaults: w3.eth.max_priority_fee,
8082
"maxFeePerGas": (
81-
lambda w3, tx: w3.eth.max_priority_fee
82-
+ (2 * w3.eth.get_block("latest")["baseFeePerGas"])
83+
lambda w3, tx, defaults: (
84+
tx.get("maxPriorityFeePerGas", defaults.get("maxPriorityFeePerGas"))
85+
+ (2 * w3.eth.get_block("latest")["baseFeePerGas"])
86+
)
8387
),
84-
"maxPriorityFeePerGas": lambda w3, tx: w3.eth.max_priority_fee,
85-
"chainId": lambda w3, tx: w3.eth.chain_id,
88+
"chainId": lambda w3, _tx, _defaults: w3.eth.chain_id,
8689
}
8790

8891
if TYPE_CHECKING:
@@ -117,7 +120,7 @@ def fill_transaction_defaults(w3: "Web3", transaction: TxParams) -> TxParams:
117120
or any_in_dict(DYNAMIC_FEE_TXN_PARAMS, transaction)
118121
)
119122

120-
defaults = {}
123+
defaults: Dict[str, Union[bytes, int]] = {}
121124
for key, default_getter in TRANSACTION_DEFAULTS.items():
122125
if key not in transaction:
123126
if (
@@ -135,7 +138,7 @@ def fill_transaction_defaults(w3: "Web3", transaction: TxParams) -> TxParams:
135138
raise Web3ValueError(
136139
f"You must specify a '{key}' value in the transaction"
137140
)
138-
default_val = default_getter(w3, transaction)
141+
default_val = default_getter(w3, transaction, defaults)
139142
else:
140143
default_val = default_getter
141144

0 commit comments

Comments
 (0)