Skip to content

Commit e8a927d

Browse files
wiseaidevreedsa
andauthored
[WIP] Implement web3.eth.Eth.createAccessList (#2381)
* 🎉 first commit. Signed-off-by: Harmouch101 <eng.mahmoudharmouch@gmail.com> * ⚡ Update. Signed-off-by: Harmouch101 <eng.mahmoudharmouch@gmail.com> * Fix lint * Finish merge of eth module * Fix typo * Passing create_access_list tests * Fix types * Remove print * Fixes for accessList docs per feedback * Formatting refactor, docs improvements per feedback * Fix tests * Fix tests * Replace eth-tester createAccessList test with not implemented * Dont call eth-tester for createAccessList * Fix test to check address as HexBytes * Adjust wording for create_access_list doc params * Update typings for response type of create_access_list, remove prints * Docs fixes * Docs fixes * News update * Remove block syntax for Transaction in create access list docs * Fix block --------- Signed-off-by: Harmouch101 <eng.mahmoudharmouch@gmail.com> Co-authored-by: Stuart Reed <stuart.reed@ethereum.org>
1 parent 38f19a4 commit e8a927d

File tree

15 files changed

+173
-13
lines changed

15 files changed

+173
-13
lines changed

docs/web3.eth.rst

+40
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,46 @@ The following methods are available on the ``web3.eth`` namespace.
987987
As of v6.3.0, the raw data is also returned and
988988
can be accessed via the ``data`` attribute on ``ContractLogicError``.
989989

990+
991+
.. py:method:: Eth.create_access_list(transaction, block_identifier=web3.eth.default_block)
992+
993+
* Delegates to ``eth_createAccessList`` RPC Method
994+
995+
This method creates an `EIP-2930 <https://eips.ethereum.org/EIPS/eip-2930>`_ type ``accessList`` based on a given
996+
``transaction``. The ``accessList`` contains all storage slots and addresses read and written by the transaction,
997+
except for the sender account and the precompiles. This method uses the same ``transaction`` call object and
998+
``block_identifier`` object as :meth:`~web3.eth.Eth.call()`. An ``accessList`` can be used to access contracts that
999+
became inaccessible due to gas cost increases.
1000+
1001+
The ``transaction`` parameter is handled in the same manner as the
1002+
:meth:`~web3.eth.Eth.send_transaction()` method.
1003+
The optional ``block_identifier`` parameter is a block_number or ``latest`` or ``pending``. Default is ``latest``.
1004+
1005+
.. code-block:: python
1006+
1007+
>>> w3.eth.create_access_list({'from': '0x0', 'data': '0x0', 'type': '0x1'})
1008+
{
1009+
'accessList': (
1010+
{
1011+
'address': '0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae',
1012+
'storageKeys': (
1013+
'0x0000000000000000000000000000000000000000000000000000000000000003',
1014+
'0x0000000000000000000000000000000000000000000000000000000000000007',
1015+
)
1016+
},
1017+
{
1018+
'address': '0xbb9bc244d798123fde783fcc1c72d3bb8c189413',
1019+
'storageKeys': ()
1020+
},
1021+
),
1022+
"gas": "21000"
1023+
}
1024+
1025+
The method ``eth_createAccessList`` returns a list of addresses and storage keys used by the transaction, plus the gas
1026+
consumed when the ``accessList`` is included. Like ``eth_estimateGas``, this is an estimation; the list could change when
1027+
the transaction is actually finalized. Adding an ``accessList`` to your transaction does not necessarily result in lower
1028+
gas usage compared to a transaction without an ``accessList``.
1029+
9901030
.. py:method:: Eth.fee_history(block_count, newest_block, reward_percentiles=None)
9911031
9921032
* Delegates to ``eth_feeHistory`` RPC Method

newsfragments/2381.feature.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Implement ``createAccessList`` RPC endpoint to create an EIP-2930 access list.

tests/core/eth-module/test_transactions.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,12 @@ def test_get_transaction_formatters(w3):
277277
{
278278
"address": checksummed_addr,
279279
"storageKeys": [
280-
"0x0000000000000000000000000000000000000000000000000000000000000032", # noqa: E501
281-
"0x0000000000000000000000000000000000000000000000000000000000000036", # noqa: E501
280+
HexBytes(
281+
"0x0000000000000000000000000000000000000000000000000000000000000032" # noqa: E501
282+
),
283+
HexBytes(
284+
"0x0000000000000000000000000000000000000000000000000000000000000036" # noqa: E501
285+
),
282286
],
283287
}
284288
),
@@ -293,5 +297,6 @@ def test_get_transaction_formatters(w3):
293297
"data": HexBytes(unformatted_transaction["data"]),
294298
}
295299
)
300+
296301
assert received_tx == expected
297302
w3.middleware_onion.remove("result_middleware")

tests/integration/test_ethereum_tester.py

+4
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,10 @@ class TestEthereumTesterEthModule(EthModuleTest):
330330
test_eth_send_transaction_with_nonce = not_implemented(
331331
EthModuleTest.test_eth_send_transaction_with_nonce, MethodUnavailable
332332
)
333+
test_eth_create_access_list = not_implemented(
334+
EthModuleTest.test_eth_create_access_list,
335+
MethodUnavailable,
336+
)
333337
test_eth_call_with_override_code = not_implemented(
334338
EthModuleTest.test_eth_call_with_override_code,
335339
TypeError,

web3/_utils/method_formatters.py

+21-11
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,24 @@ def type_aware_apply_formatters_to_dict_keys_and_values(
186186
)
187187

188188

189+
def apply_list_to_array_formatter(formatter: Any) -> Callable[..., Any]:
190+
return to_list(apply_formatter_to_array(formatter))
191+
192+
193+
ACCESS_LIST_FORMATTER = type_aware_apply_formatters_to_dict(
194+
{
195+
"address": to_checksum_address,
196+
"storageKeys": apply_list_to_array_formatter(to_hexbytes(64)),
197+
}
198+
)
199+
200+
ACCESS_LIST_RESPONSE_FORMATTER = type_aware_apply_formatters_to_dict(
201+
{
202+
"accessList": apply_list_to_array_formatter(ACCESS_LIST_FORMATTER),
203+
"gasUsed": to_integer_if_hex,
204+
}
205+
)
206+
189207
TRANSACTION_RESULT_FORMATTERS = {
190208
"blockHash": apply_formatter_if(is_not_null, to_hexbytes(32)),
191209
"blockNumber": apply_formatter_if(is_not_null, to_integer_if_hex),
@@ -210,13 +228,7 @@ def type_aware_apply_formatters_to_dict_keys_and_values(
210228
"chainId": apply_formatter_if(is_not_null, to_integer_if_hex),
211229
"accessList": apply_formatter_if(
212230
is_not_null,
213-
apply_formatter_to_array(
214-
type_aware_apply_formatters_to_dict(
215-
{
216-
"address": to_checksum_address,
217-
}
218-
)
219-
),
231+
apply_formatter_to_array(ACCESS_LIST_FORMATTER),
220232
),
221233
"input": HexBytes,
222234
"data": HexBytes, # Nethermind, for example, returns both `input` and `data`
@@ -238,10 +250,6 @@ def type_aware_apply_formatters_to_dict_keys_and_values(
238250
)
239251

240252

241-
def apply_list_to_array_formatter(formatter: Any) -> Callable[..., Any]:
242-
return to_list(apply_formatter_to_array(formatter))
243-
244-
245253
LOG_ENTRY_FORMATTERS = {
246254
"blockHash": apply_formatter_if(is_not_null, to_hexbytes(32)),
247255
"blockNumber": apply_formatter_if(is_not_null, to_integer_if_hex),
@@ -517,6 +525,7 @@ def apply_list_to_array_formatter(formatter: Any) -> Callable[..., Any]:
517525
(is_length(3), call_with_override),
518526
)
519527
),
528+
RPC.eth_createAccessList: apply_formatter_at_index(transaction_param_formatter, 0),
520529
RPC.eth_estimateGas: apply_one_of_formatters(
521530
(
522531
(is_length(1), estimate_gas_without_block_id),
@@ -682,6 +691,7 @@ def subscription_formatter(value: Any) -> Union[HexBytes, HexStr, Dict[str, Any]
682691
RPC.eth_chainId: to_integer_if_hex,
683692
RPC.eth_coinbase: to_checksum_address,
684693
RPC.eth_call: HexBytes,
694+
RPC.eth_createAccessList: ACCESS_LIST_RESPONSE_FORMATTER,
685695
RPC.eth_estimateGas: to_integer_if_hex,
686696
RPC.eth_feeHistory: fee_history_formatter,
687697
RPC.eth_maxPriorityFeePerGas: to_integer_if_hex,

web3/_utils/module_testing/eth_module.py

+54
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,28 @@
112112
# "web3py" as an abi-encoded string
113113
WEB3PY_AS_HEXBYTES = "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000067765623370790000000000000000000000000000000000000000000000000000" # noqa: E501
114114

115+
RLP_ACCESS_LIST = [
116+
(
117+
"0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
118+
(
119+
"0x0000000000000000000000000000000000000000000000000000000000000003",
120+
"0x0000000000000000000000000000000000000000000000000000000000000007",
121+
),
122+
),
123+
("0xbb9bc244d798123fde783fcc1c72d3bb8c189413", ()),
124+
]
125+
126+
RPC_ACCESS_LIST = [
127+
{
128+
"address": "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
129+
"storageKeys": (
130+
"0x0000000000000000000000000000000000000000000000000000000000000003",
131+
"0x0000000000000000000000000000000000000000000000000000000000000007",
132+
),
133+
},
134+
{"address": "0xbb9bc244d798123fde783fcc1c72d3bb8c189413", "storageKeys": ()},
135+
]
136+
115137
if TYPE_CHECKING:
116138
from _pytest.monkeypatch import MonkeyPatch # noqa: F401
117139

@@ -2672,6 +2694,38 @@ def test_eth_get_code_with_block_identifier(
26722694
assert isinstance(code, HexBytes)
26732695
assert len(code) > 0
26742696

2697+
def test_eth_create_access_list(
2698+
self,
2699+
w3: "Web3",
2700+
unlocked_account_dual_type: ChecksumAddress,
2701+
math_contract: "Contract",
2702+
) -> None:
2703+
# Initialize transaction for gas estimation
2704+
txn_params: TxParams = {
2705+
"from": unlocked_account_dual_type,
2706+
"value": Wei(1),
2707+
"gas": 21000,
2708+
}
2709+
2710+
txn = math_contract.functions.incrementCounter(1).build_transaction(txn_params)
2711+
2712+
# create access list using data from transaction
2713+
response = w3.eth.create_access_list(
2714+
{
2715+
"from": unlocked_account_dual_type,
2716+
"to": math_contract.address,
2717+
"data": txn["data"],
2718+
}
2719+
)
2720+
2721+
assert is_dict(response)
2722+
access_list = response["accessList"]
2723+
assert len(access_list) > 0
2724+
assert access_list[0]["address"] is not None
2725+
assert is_checksum_address(access_list[0]["address"])
2726+
assert len(access_list[0]["storageKeys"][0]) == 32
2727+
assert int(response["gasUsed"]) >= 0
2728+
26752729
def test_eth_sign(
26762730
self, w3: "Web3", unlocked_account_dual_type: ChecksumAddress
26772731
) -> None:

web3/_utils/rpc_abi.py

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class RPC:
4747
eth_accounts = RPCEndpoint("eth_accounts")
4848
eth_blockNumber = RPCEndpoint("eth_blockNumber")
4949
eth_call = RPCEndpoint("eth_call")
50+
eth_createAccessList = RPCEndpoint("eth_createAccessList")
5051
eth_chainId = RPCEndpoint("eth_chainId")
5152
eth_coinbase = RPCEndpoint("eth_coinbase")
5253
eth_estimateGas = RPCEndpoint("eth_estimateGas")
@@ -191,6 +192,7 @@ class RPC:
191192
RPC_ABIS: Dict[str, Union[Sequence[Any], Dict[str, str]]] = {
192193
# eth
193194
"eth_call": TRANSACTION_PARAMS_ABIS,
195+
"eth_createAccessList": TRANSACTION_PARAMS_ABIS,
194196
"eth_estimateGas": TRANSACTION_PARAMS_ABIS,
195197
"eth_getBalance": ["address", None],
196198
"eth_getBlockByHash": ["bytes32", "bool"],

web3/eth/base_eth.py

+13
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,19 @@ def call_munger(
152152
else:
153153
return (transaction, block_identifier, state_override)
154154

155+
def create_access_list_munger(
156+
self, transaction: TxParams, block_identifier: Optional[BlockIdentifier] = None
157+
) -> Tuple[TxParams, BlockIdentifier]:
158+
# TODO: move to middleware
159+
if "from" not in transaction and is_checksum_address(self.default_account):
160+
transaction = assoc(transaction, "from", self.default_account)
161+
162+
# TODO: move to middleware
163+
if block_identifier is None:
164+
block_identifier = self.default_block
165+
166+
return (transaction, block_identifier)
167+
155168
def sign_munger(
156169
self,
157170
account: Union[Address, ChecksumAddress, ENS],

web3/eth/eth.py

+17
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
BlockIdentifier,
7373
BlockParams,
7474
CallOverride,
75+
CreateAccessListResponse,
7576
FeeHistory,
7677
FilterParams,
7778
LogReceipt,
@@ -281,6 +282,22 @@ def _durin_call(
281282

282283
raise TooManyRequests("Too many CCIP read redirects")
283284

285+
# eth_createAccessList
286+
287+
_create_access_list: Method[
288+
Callable[
289+
[TxParams, Optional[BlockIdentifier]],
290+
CreateAccessListResponse,
291+
]
292+
] = Method(RPC.eth_createAccessList, mungers=[BaseEth.create_access_list_munger])
293+
294+
def create_access_list(
295+
self,
296+
transaction: TxParams,
297+
block_identifier: Optional[BlockIdentifier] = None,
298+
) -> CreateAccessListResponse:
299+
return self._create_access_list(transaction, block_identifier)
300+
284301
# eth_estimateGas
285302

286303
_estimate_gas: Method[

web3/middleware/cache.py

+1
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ def middleware(method: RPCEndpoint, params: Any) -> RPCResponse:
211211
"eth_getUncleCountByBlockNumber",
212212
"eth_getCode",
213213
"eth_call",
214+
"eth_createAccessList",
214215
"eth_estimateGas",
215216
"eth_getBlockByNumber",
216217
"eth_getTransactionByBlockNumberAndIndex",

web3/middleware/exception_retry_request.py

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"eth_getTransactionCount",
6464
"eth_getRawTransactionByHash",
6565
"eth_call",
66+
"eth_createAccessList",
6667
"eth_estimateGas",
6768
"eth_maxPriorityFeePerGas",
6869
"eth_newBlockFilter",

web3/middleware/validation.py

+1
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ def _transaction_param_validator(web3_chain_id: int) -> Callable[..., Any]:
111111
RPC.eth_sendTransaction,
112112
RPC.eth_estimateGas,
113113
RPC.eth_call,
114+
RPC.eth_createAccessList,
114115
]
115116

116117

web3/providers/eth_tester/defaults.py

+1
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ def personal_send_transaction(eth_tester: "EthereumTester", params: Any) -> HexS
311311
"sendTransaction": call_eth_tester("send_transaction"),
312312
"sendRawTransaction": call_eth_tester("send_raw_transaction"),
313313
"call": call_eth_tester("call"), # TODO: untested
314+
"createAccessList": not_implemented,
314315
"estimateGas": call_eth_tester("estimate_gas"), # TODO: untested
315316
"getBlockByHash": null_if_block_not_found(call_eth_tester("get_block_by_hash")),
316317
"getBlockByNumber": null_if_block_not_found(

web3/providers/eth_tester/middleware.py

+5
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,10 @@ def is_hexstr(value: Any) -> bool:
250250
transaction_request_transformer,
251251
apply_formatter_if(is_not_named_block, to_integer_if_hex),
252252
),
253+
RPCEndpoint("eth_createAccessList"): apply_formatters_to_args(
254+
transaction_request_transformer,
255+
apply_formatter_if(is_not_named_block, to_integer_if_hex),
256+
),
253257
RPCEndpoint("eth_uninstallFilter"): apply_formatters_to_args(hex_to_integer),
254258
RPCEndpoint("eth_getCode"): apply_formatters_to_args(
255259
identity,
@@ -346,6 +350,7 @@ def middleware(method: RPCEndpoint, params: Any) -> RPCResponse:
346350
"eth_call",
347351
"eth_estimateGas",
348352
"eth_sendTransaction",
353+
"eth_createAccessList",
349354
):
350355
fill_default_from = fill_default("from", guess_from, w3)
351356
filled_transaction = pipe(

web3/types.py

+5
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,11 @@ class FormattedEthSubscriptionResponse(TypedDict):
311311
]
312312

313313

314+
class CreateAccessListResponse(TypedDict):
315+
accessList: AccessList
316+
gasUsed: int
317+
318+
314319
Middleware = Callable[[Callable[[RPCEndpoint, Any], RPCResponse], "Web3"], Any]
315320
AsyncMiddlewareCoroutine = Callable[
316321
[RPCEndpoint, Any], Coroutine[Any, Any, RPCResponse]

0 commit comments

Comments
 (0)