Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Implement web3.eth.Eth.createAccessList #2381

Merged
merged 22 commits into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions docs/web3.eth.rst
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,46 @@ The following methods are available on the ``web3.eth`` namespace.
As of v6.3.0, the raw data is also returned and
can be accessed via the ``data`` attribute on ``ContractLogicError``.


.. py:method:: Eth.create_access_list(transaction, block_identifier=web3.eth.default_block)

* Delegates to ``eth_createAccessList`` RPC Method

This method creates an `EIP-2930 <https://eips.ethereum.org/EIPS/eip-2930>`_ type ``accessList`` based on a given
``Transaction``. The ``accessList`` contains all storage slots and addresses read and written by the transaction,
except for the sender account and the precompiles. This method uses the same ``transaction`` call object and
``block_identifier`` object as :meth:`~web3.eth.Eth.call()`. An ``accessList`` can be used to access contracts that
became inaccessible due to gas cost increases.

The ``transaction`` parameter is handled in the same manner as the
:meth:`~web3.eth.Eth.send_transaction()` method.
The optional ``block_identifier`` parameter is a block_number or ``latest`` or ``pending``. Default is ``latest``.

.. code-block:: python

>>> w3.eth.create_access_list({'from': '0x0', 'data': '0x0', 'type': '0x1'})
{
'accessList': (
{
'address': '0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae',
'storageKeys': (
'0x0000000000000000000000000000000000000000000000000000000000000003',
'0x0000000000000000000000000000000000000000000000000000000000000007',
)
},
{
'address': '0xbb9bc244d798123fde783fcc1c72d3bb8c189413',
'storageKeys': ()
},
),
"gas": "21000"
}

The method ``eth_createAccessList`` returns a list of addresses and storage keys used by the transaction, plus the gas
consumed when the ``accessList`` is included. Like ``eth_estimateGas``, this is an estimation; the list could change when
the transaction is actually finalized. Adding an ``accessList`` to your transaction does not necessarily result in lower
gas usage compared to a transaction without an ``accessList``.

.. py:method:: Eth.fee_history(block_count, newest_block, reward_percentiles=None)

* Delegates to ``eth_feeHistory`` RPC Method
Expand Down
1 change: 1 addition & 0 deletions newsfragments/2381.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implement ``createAccessList`` RPC endpoint to create an EIP-2930 access list.
9 changes: 7 additions & 2 deletions tests/core/eth-module/test_transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,12 @@ def test_get_transaction_formatters(w3):
{
"address": checksummed_addr,
"storageKeys": [
"0x0000000000000000000000000000000000000000000000000000000000000032", # noqa: E501
"0x0000000000000000000000000000000000000000000000000000000000000036", # noqa: E501
HexBytes(
"0x0000000000000000000000000000000000000000000000000000000000000032" # noqa: E501
),
HexBytes(
"0x0000000000000000000000000000000000000000000000000000000000000036" # noqa: E501
),
],
}
),
Expand All @@ -293,5 +297,6 @@ def test_get_transaction_formatters(w3):
"data": HexBytes(unformatted_transaction["data"]),
}
)

assert received_tx == expected
w3.middleware_onion.remove("result_middleware")
4 changes: 4 additions & 0 deletions tests/integration/test_ethereum_tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,10 @@ class TestEthereumTesterEthModule(EthModuleTest):
test_eth_send_transaction_with_nonce = not_implemented(
EthModuleTest.test_eth_send_transaction_with_nonce, MethodUnavailable
)
test_eth_create_access_list = not_implemented(
EthModuleTest.test_eth_create_access_list,
MethodUnavailable,
)
test_eth_call_with_override_code = not_implemented(
EthModuleTest.test_eth_call_with_override_code,
TypeError,
Expand Down
32 changes: 21 additions & 11 deletions web3/_utils/method_formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,24 @@ def type_aware_apply_formatters_to_dict_keys_and_values(
)


def apply_list_to_array_formatter(formatter: Any) -> Callable[..., Any]:
return to_list(apply_formatter_to_array(formatter))


ACCESS_LIST_FORMATTER = type_aware_apply_formatters_to_dict(
{
"address": to_checksum_address,
"storageKeys": apply_list_to_array_formatter(to_hexbytes(64)),
}
)

ACCESS_LIST_RESPONSE_FORMATTER = type_aware_apply_formatters_to_dict(
{
"accessList": apply_list_to_array_formatter(ACCESS_LIST_FORMATTER),
"gasUsed": to_integer_if_hex,
}
)

TRANSACTION_RESULT_FORMATTERS = {
"blockHash": apply_formatter_if(is_not_null, to_hexbytes(32)),
"blockNumber": apply_formatter_if(is_not_null, to_integer_if_hex),
Expand All @@ -210,13 +228,7 @@ def type_aware_apply_formatters_to_dict_keys_and_values(
"chainId": apply_formatter_if(is_not_null, to_integer_if_hex),
"accessList": apply_formatter_if(
is_not_null,
apply_formatter_to_array(
type_aware_apply_formatters_to_dict(
{
"address": to_checksum_address,
}
)
),
apply_formatter_to_array(ACCESS_LIST_FORMATTER),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌🏼

),
"input": HexBytes,
"data": HexBytes, # Nethermind, for example, returns both `input` and `data`
Expand All @@ -238,10 +250,6 @@ def type_aware_apply_formatters_to_dict_keys_and_values(
)


def apply_list_to_array_formatter(formatter: Any) -> Callable[..., Any]:
return to_list(apply_formatter_to_array(formatter))


LOG_ENTRY_FORMATTERS = {
"blockHash": apply_formatter_if(is_not_null, to_hexbytes(32)),
"blockNumber": apply_formatter_if(is_not_null, to_integer_if_hex),
Expand Down Expand Up @@ -517,6 +525,7 @@ def apply_list_to_array_formatter(formatter: Any) -> Callable[..., Any]:
(is_length(3), call_with_override),
)
),
RPC.eth_createAccessList: apply_formatter_at_index(transaction_param_formatter, 0),
RPC.eth_estimateGas: apply_one_of_formatters(
(
(is_length(1), estimate_gas_without_block_id),
Expand Down Expand Up @@ -682,6 +691,7 @@ def subscription_formatter(value: Any) -> Union[HexBytes, HexStr, Dict[str, Any]
RPC.eth_chainId: to_integer_if_hex,
RPC.eth_coinbase: to_checksum_address,
RPC.eth_call: HexBytes,
RPC.eth_createAccessList: ACCESS_LIST_RESPONSE_FORMATTER,
RPC.eth_estimateGas: to_integer_if_hex,
RPC.eth_feeHistory: fee_history_formatter,
RPC.eth_maxPriorityFeePerGas: to_integer_if_hex,
Expand Down
54 changes: 54 additions & 0 deletions web3/_utils/module_testing/eth_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,28 @@
# "web3py" as an abi-encoded string
WEB3PY_AS_HEXBYTES = "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000067765623370790000000000000000000000000000000000000000000000000000" # noqa: E501

RLP_ACCESS_LIST = [
(
"0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
(
"0x0000000000000000000000000000000000000000000000000000000000000003",
"0x0000000000000000000000000000000000000000000000000000000000000007",
),
),
("0xbb9bc244d798123fde783fcc1c72d3bb8c189413", ()),
]

RPC_ACCESS_LIST = [
{
"address": "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
"storageKeys": (
"0x0000000000000000000000000000000000000000000000000000000000000003",
"0x0000000000000000000000000000000000000000000000000000000000000007",
),
},
{"address": "0xbb9bc244d798123fde783fcc1c72d3bb8c189413", "storageKeys": ()},
]

if TYPE_CHECKING:
from _pytest.monkeypatch import MonkeyPatch # noqa: F401

Expand Down Expand Up @@ -2672,6 +2694,38 @@ def test_eth_get_code_with_block_identifier(
assert isinstance(code, HexBytes)
assert len(code) > 0

def test_eth_create_access_list(
self,
w3: "Web3",
unlocked_account_dual_type: ChecksumAddress,
math_contract: "Contract",
) -> None:
# Initialize transaction for gas estimation
txn_params: TxParams = {
"from": unlocked_account_dual_type,
"value": Wei(1),
"gas": 21000,
}

txn = math_contract.functions.incrementCounter(1).build_transaction(txn_params)

# create access list using data from transaction
response = w3.eth.create_access_list(
{
"from": unlocked_account_dual_type,
"to": math_contract.address,
"data": txn["data"],
}
)

assert is_dict(response)
access_list = response["accessList"]
assert len(access_list) > 0
assert access_list[0]["address"] is not None
assert is_checksum_address(access_list[0]["address"])
assert len(access_list[0]["storageKeys"][0]) == 32
assert int(response["gasUsed"]) >= 0

def test_eth_sign(
self, w3: "Web3", unlocked_account_dual_type: ChecksumAddress
) -> None:
Expand Down
2 changes: 2 additions & 0 deletions web3/_utils/rpc_abi.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class RPC:
eth_accounts = RPCEndpoint("eth_accounts")
eth_blockNumber = RPCEndpoint("eth_blockNumber")
eth_call = RPCEndpoint("eth_call")
eth_createAccessList = RPCEndpoint("eth_createAccessList")
eth_chainId = RPCEndpoint("eth_chainId")
eth_coinbase = RPCEndpoint("eth_coinbase")
eth_estimateGas = RPCEndpoint("eth_estimateGas")
Expand Down Expand Up @@ -191,6 +192,7 @@ class RPC:
RPC_ABIS: Dict[str, Union[Sequence[Any], Dict[str, str]]] = {
# eth
"eth_call": TRANSACTION_PARAMS_ABIS,
"eth_createAccessList": TRANSACTION_PARAMS_ABIS,
"eth_estimateGas": TRANSACTION_PARAMS_ABIS,
"eth_getBalance": ["address", None],
"eth_getBlockByHash": ["bytes32", "bool"],
Expand Down
13 changes: 13 additions & 0 deletions web3/eth/base_eth.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,19 @@ def call_munger(
else:
return (transaction, block_identifier, state_override)

def create_access_list_munger(
self, transaction: TxParams, block_identifier: Optional[BlockIdentifier] = None
) -> Tuple[TxParams, BlockIdentifier]:
# TODO: move to middleware
if "from" not in transaction and is_checksum_address(self.default_account):
transaction = assoc(transaction, "from", self.default_account)

# TODO: move to middleware
if block_identifier is None:
block_identifier = self.default_block

return (transaction, block_identifier)

def sign_munger(
self,
account: Union[Address, ChecksumAddress, ENS],
Expand Down
17 changes: 17 additions & 0 deletions web3/eth/eth.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
BlockIdentifier,
BlockParams,
CallOverride,
CreateAccessListResponse,
FeeHistory,
FilterParams,
LogReceipt,
Expand Down Expand Up @@ -281,6 +282,22 @@ def _durin_call(

raise TooManyRequests("Too many CCIP read redirects")

# eth_createAccessList

_create_access_list: Method[
Callable[
[TxParams, Optional[BlockIdentifier]],
CreateAccessListResponse,
]
] = Method(RPC.eth_createAccessList, mungers=[BaseEth.create_access_list_munger])

def create_access_list(
self,
transaction: TxParams,
block_identifier: Optional[BlockIdentifier] = None,
) -> CreateAccessListResponse:
return self._create_access_list(transaction, block_identifier)

# eth_estimateGas

_estimate_gas: Method[
Expand Down
1 change: 1 addition & 0 deletions web3/middleware/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ def middleware(method: RPCEndpoint, params: Any) -> RPCResponse:
"eth_getUncleCountByBlockNumber",
"eth_getCode",
"eth_call",
"eth_createAccessList",
"eth_estimateGas",
"eth_getBlockByNumber",
"eth_getTransactionByBlockNumberAndIndex",
Expand Down
1 change: 1 addition & 0 deletions web3/middleware/exception_retry_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"eth_getTransactionCount",
"eth_getRawTransactionByHash",
"eth_call",
"eth_createAccessList",
"eth_estimateGas",
"eth_maxPriorityFeePerGas",
"eth_newBlockFilter",
Expand Down
1 change: 1 addition & 0 deletions web3/middleware/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ def _transaction_param_validator(web3_chain_id: int) -> Callable[..., Any]:
RPC.eth_sendTransaction,
RPC.eth_estimateGas,
RPC.eth_call,
RPC.eth_createAccessList,
]


Expand Down
1 change: 1 addition & 0 deletions web3/providers/eth_tester/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ def personal_send_transaction(eth_tester: "EthereumTester", params: Any) -> HexS
"sendTransaction": call_eth_tester("send_transaction"),
"sendRawTransaction": call_eth_tester("send_raw_transaction"),
"call": call_eth_tester("call"), # TODO: untested
"createAccessList": not_implemented,
"estimateGas": call_eth_tester("estimate_gas"), # TODO: untested
"getBlockByHash": null_if_block_not_found(call_eth_tester("get_block_by_hash")),
"getBlockByNumber": null_if_block_not_found(
Expand Down
5 changes: 5 additions & 0 deletions web3/providers/eth_tester/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,10 @@ def is_hexstr(value: Any) -> bool:
transaction_request_transformer,
apply_formatter_if(is_not_named_block, to_integer_if_hex),
),
RPCEndpoint("eth_createAccessList"): apply_formatters_to_args(
transaction_request_transformer,
apply_formatter_if(is_not_named_block, to_integer_if_hex),
),
RPCEndpoint("eth_uninstallFilter"): apply_formatters_to_args(hex_to_integer),
RPCEndpoint("eth_getCode"): apply_formatters_to_args(
identity,
Expand Down Expand Up @@ -346,6 +350,7 @@ def middleware(method: RPCEndpoint, params: Any) -> RPCResponse:
"eth_call",
"eth_estimateGas",
"eth_sendTransaction",
"eth_createAccessList",
):
fill_default_from = fill_default("from", guess_from, w3)
filled_transaction = pipe(
Expand Down
5 changes: 5 additions & 0 deletions web3/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,11 @@ class FormattedEthSubscriptionResponse(TypedDict):
]


class CreateAccessListResponse(TypedDict):
accessList: AccessList
gasUsed: int


Middleware = Callable[[Callable[[RPCEndpoint, Any], RPCResponse], "Web3"], Any]
AsyncMiddlewareCoroutine = Callable[
[RPCEndpoint, Any], Coroutine[Any, Any, RPCResponse]
Expand Down