diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index c0bd9d5f745..03afd604692 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -77,7 +77,6 @@ set_account_balance, ) from .transactions import ( - AccessListTransaction, BlobTransaction, FeeMarketTransaction, LegacyTransaction, @@ -86,6 +85,7 @@ decode_transaction, encode_transaction, get_transaction_hash, + has_access_list, recover_sender, validate_transaction, ) @@ -998,15 +998,7 @@ def process_transaction( access_list_addresses = set() access_list_storage_keys = set() access_list_addresses.add(block_env.coinbase) - if isinstance( - tx, - ( - AccessListTransaction, - FeeMarketTransaction, - BlobTransaction, - SetCodeTransaction, - ), - ): + if has_access_list(tx): for access in tx.access_list: access_list_addresses.add(access.account) for slot in access.slots: diff --git a/src/ethereum/forks/amsterdam/transactions.py b/src/ethereum/forks/amsterdam/transactions.py index 7351227df6f..da7dfd13f5e 100644 --- a/src/ethereum/forks/amsterdam/transactions.py +++ b/src/ethereum/forks/amsterdam/transactions.py @@ -5,7 +5,7 @@ """ from dataclasses import dataclass -from typing import Tuple +from typing import Tuple, TypeGuard from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes32 @@ -481,6 +481,23 @@ class SetCodeTransaction: """ +AccessListCapableTransaction = ( + AccessListTransaction + | FeeMarketTransaction + | BlobTransaction + | SetCodeTransaction +) +""" +Transaction types that include an [EIP-2930]-style access list. + +See [`has_access_list`][hal] and [`Access`][a] for more details. + +[EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 +[hal]: ref:ethereum.forks.amsterdam.transactions.has_access_list +[a]: ref:ethereum.forks.amsterdam.transactions.Access +""" + + def encode_transaction(tx: Transaction) -> LegacyTransaction | Bytes: """ Encode a transaction into its RLP or typed transaction format. @@ -622,15 +639,7 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: create_cost = Uint(0) access_list_cost = Uint(0) - if isinstance( - tx, - ( - AccessListTransaction, - FeeMarketTransaction, - BlobTransaction, - SetCodeTransaction, - ), - ): + if has_access_list(tx): for access in tx.access_list: access_list_cost += GAS_TX_ACCESS_LIST_ADDRESS access_list_cost += ( @@ -891,3 +900,17 @@ def get_transaction_hash(tx: Bytes | LegacyTransaction) -> Hash32: return keccak256(rlp.encode(tx)) else: return keccak256(tx) + + +def has_access_list( + tx: Transaction, +) -> TypeGuard[AccessListCapableTransaction]: + """ + Return whether the transaction has an [EIP-2930]-style access list. + + [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 + """ + return isinstance( + tx, + AccessListCapableTransaction, + ) diff --git a/src/ethereum/forks/bpo1/fork.py b/src/ethereum/forks/bpo1/fork.py index 3dfd35f98a8..7e6b5ee46de 100644 --- a/src/ethereum/forks/bpo1/fork.py +++ b/src/ethereum/forks/bpo1/fork.py @@ -63,7 +63,6 @@ state_root, ) from .transactions import ( - AccessListTransaction, BlobTransaction, FeeMarketTransaction, LegacyTransaction, @@ -72,6 +71,7 @@ decode_transaction, encode_transaction, get_transaction_hash, + has_access_list, recover_sender, validate_transaction, ) @@ -916,15 +916,7 @@ def process_transaction( access_list_addresses = set() access_list_storage_keys = set() access_list_addresses.add(block_env.coinbase) - if isinstance( - tx, - ( - AccessListTransaction, - FeeMarketTransaction, - BlobTransaction, - SetCodeTransaction, - ), - ): + if has_access_list(tx): for access in tx.access_list: access_list_addresses.add(access.account) for slot in access.slots: diff --git a/src/ethereum/forks/bpo1/transactions.py b/src/ethereum/forks/bpo1/transactions.py index 048852529e4..c8f1826324e 100644 --- a/src/ethereum/forks/bpo1/transactions.py +++ b/src/ethereum/forks/bpo1/transactions.py @@ -5,7 +5,7 @@ """ from dataclasses import dataclass -from typing import Tuple +from typing import Tuple, TypeGuard from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes32 @@ -481,6 +481,23 @@ class SetCodeTransaction: """ +AccessListCapableTransaction = ( + AccessListTransaction + | FeeMarketTransaction + | BlobTransaction + | SetCodeTransaction +) +""" +Transaction types that include an [EIP-2930]-style access list. + +See [`has_access_list`][hal] and [`Access`][a] for more details. + +[EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 +[hal]: ref:ethereum.forks.amsterdam.transactions.has_access_list +[a]: ref:ethereum.forks.amsterdam.transactions.Access +""" + + def encode_transaction(tx: Transaction) -> LegacyTransaction | Bytes: """ Encode a transaction into its RLP or typed transaction format. @@ -615,15 +632,7 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: create_cost = Uint(0) access_list_cost = Uint(0) - if isinstance( - tx, - ( - AccessListTransaction, - FeeMarketTransaction, - BlobTransaction, - SetCodeTransaction, - ), - ): + if has_access_list(tx): for access in tx.access_list: access_list_cost += GAS_TX_ACCESS_LIST_ADDRESS access_list_cost += ( @@ -884,3 +893,17 @@ def get_transaction_hash(tx: Bytes | LegacyTransaction) -> Hash32: return keccak256(rlp.encode(tx)) else: return keccak256(tx) + + +def has_access_list( + tx: Transaction, +) -> TypeGuard[AccessListCapableTransaction]: + """ + Return whether the transaction has an [EIP-2930]-style access list. + + [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 + """ + return isinstance( + tx, + AccessListCapableTransaction, + ) diff --git a/src/ethereum/forks/bpo2/fork.py b/src/ethereum/forks/bpo2/fork.py index 3dfd35f98a8..7e6b5ee46de 100644 --- a/src/ethereum/forks/bpo2/fork.py +++ b/src/ethereum/forks/bpo2/fork.py @@ -63,7 +63,6 @@ state_root, ) from .transactions import ( - AccessListTransaction, BlobTransaction, FeeMarketTransaction, LegacyTransaction, @@ -72,6 +71,7 @@ decode_transaction, encode_transaction, get_transaction_hash, + has_access_list, recover_sender, validate_transaction, ) @@ -916,15 +916,7 @@ def process_transaction( access_list_addresses = set() access_list_storage_keys = set() access_list_addresses.add(block_env.coinbase) - if isinstance( - tx, - ( - AccessListTransaction, - FeeMarketTransaction, - BlobTransaction, - SetCodeTransaction, - ), - ): + if has_access_list(tx): for access in tx.access_list: access_list_addresses.add(access.account) for slot in access.slots: diff --git a/src/ethereum/forks/bpo2/transactions.py b/src/ethereum/forks/bpo2/transactions.py index 048852529e4..c8f1826324e 100644 --- a/src/ethereum/forks/bpo2/transactions.py +++ b/src/ethereum/forks/bpo2/transactions.py @@ -5,7 +5,7 @@ """ from dataclasses import dataclass -from typing import Tuple +from typing import Tuple, TypeGuard from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes32 @@ -481,6 +481,23 @@ class SetCodeTransaction: """ +AccessListCapableTransaction = ( + AccessListTransaction + | FeeMarketTransaction + | BlobTransaction + | SetCodeTransaction +) +""" +Transaction types that include an [EIP-2930]-style access list. + +See [`has_access_list`][hal] and [`Access`][a] for more details. + +[EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 +[hal]: ref:ethereum.forks.amsterdam.transactions.has_access_list +[a]: ref:ethereum.forks.amsterdam.transactions.Access +""" + + def encode_transaction(tx: Transaction) -> LegacyTransaction | Bytes: """ Encode a transaction into its RLP or typed transaction format. @@ -615,15 +632,7 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: create_cost = Uint(0) access_list_cost = Uint(0) - if isinstance( - tx, - ( - AccessListTransaction, - FeeMarketTransaction, - BlobTransaction, - SetCodeTransaction, - ), - ): + if has_access_list(tx): for access in tx.access_list: access_list_cost += GAS_TX_ACCESS_LIST_ADDRESS access_list_cost += ( @@ -884,3 +893,17 @@ def get_transaction_hash(tx: Bytes | LegacyTransaction) -> Hash32: return keccak256(rlp.encode(tx)) else: return keccak256(tx) + + +def has_access_list( + tx: Transaction, +) -> TypeGuard[AccessListCapableTransaction]: + """ + Return whether the transaction has an [EIP-2930]-style access list. + + [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 + """ + return isinstance( + tx, + AccessListCapableTransaction, + ) diff --git a/src/ethereum/forks/bpo3/fork.py b/src/ethereum/forks/bpo3/fork.py index 3dfd35f98a8..7e6b5ee46de 100644 --- a/src/ethereum/forks/bpo3/fork.py +++ b/src/ethereum/forks/bpo3/fork.py @@ -63,7 +63,6 @@ state_root, ) from .transactions import ( - AccessListTransaction, BlobTransaction, FeeMarketTransaction, LegacyTransaction, @@ -72,6 +71,7 @@ decode_transaction, encode_transaction, get_transaction_hash, + has_access_list, recover_sender, validate_transaction, ) @@ -916,15 +916,7 @@ def process_transaction( access_list_addresses = set() access_list_storage_keys = set() access_list_addresses.add(block_env.coinbase) - if isinstance( - tx, - ( - AccessListTransaction, - FeeMarketTransaction, - BlobTransaction, - SetCodeTransaction, - ), - ): + if has_access_list(tx): for access in tx.access_list: access_list_addresses.add(access.account) for slot in access.slots: diff --git a/src/ethereum/forks/bpo3/transactions.py b/src/ethereum/forks/bpo3/transactions.py index 048852529e4..c8f1826324e 100644 --- a/src/ethereum/forks/bpo3/transactions.py +++ b/src/ethereum/forks/bpo3/transactions.py @@ -5,7 +5,7 @@ """ from dataclasses import dataclass -from typing import Tuple +from typing import Tuple, TypeGuard from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes32 @@ -481,6 +481,23 @@ class SetCodeTransaction: """ +AccessListCapableTransaction = ( + AccessListTransaction + | FeeMarketTransaction + | BlobTransaction + | SetCodeTransaction +) +""" +Transaction types that include an [EIP-2930]-style access list. + +See [`has_access_list`][hal] and [`Access`][a] for more details. + +[EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 +[hal]: ref:ethereum.forks.amsterdam.transactions.has_access_list +[a]: ref:ethereum.forks.amsterdam.transactions.Access +""" + + def encode_transaction(tx: Transaction) -> LegacyTransaction | Bytes: """ Encode a transaction into its RLP or typed transaction format. @@ -615,15 +632,7 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: create_cost = Uint(0) access_list_cost = Uint(0) - if isinstance( - tx, - ( - AccessListTransaction, - FeeMarketTransaction, - BlobTransaction, - SetCodeTransaction, - ), - ): + if has_access_list(tx): for access in tx.access_list: access_list_cost += GAS_TX_ACCESS_LIST_ADDRESS access_list_cost += ( @@ -884,3 +893,17 @@ def get_transaction_hash(tx: Bytes | LegacyTransaction) -> Hash32: return keccak256(rlp.encode(tx)) else: return keccak256(tx) + + +def has_access_list( + tx: Transaction, +) -> TypeGuard[AccessListCapableTransaction]: + """ + Return whether the transaction has an [EIP-2930]-style access list. + + [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 + """ + return isinstance( + tx, + AccessListCapableTransaction, + ) diff --git a/src/ethereum/forks/bpo4/fork.py b/src/ethereum/forks/bpo4/fork.py index 3dfd35f98a8..7e6b5ee46de 100644 --- a/src/ethereum/forks/bpo4/fork.py +++ b/src/ethereum/forks/bpo4/fork.py @@ -63,7 +63,6 @@ state_root, ) from .transactions import ( - AccessListTransaction, BlobTransaction, FeeMarketTransaction, LegacyTransaction, @@ -72,6 +71,7 @@ decode_transaction, encode_transaction, get_transaction_hash, + has_access_list, recover_sender, validate_transaction, ) @@ -916,15 +916,7 @@ def process_transaction( access_list_addresses = set() access_list_storage_keys = set() access_list_addresses.add(block_env.coinbase) - if isinstance( - tx, - ( - AccessListTransaction, - FeeMarketTransaction, - BlobTransaction, - SetCodeTransaction, - ), - ): + if has_access_list(tx): for access in tx.access_list: access_list_addresses.add(access.account) for slot in access.slots: diff --git a/src/ethereum/forks/bpo4/transactions.py b/src/ethereum/forks/bpo4/transactions.py index 048852529e4..c8f1826324e 100644 --- a/src/ethereum/forks/bpo4/transactions.py +++ b/src/ethereum/forks/bpo4/transactions.py @@ -5,7 +5,7 @@ """ from dataclasses import dataclass -from typing import Tuple +from typing import Tuple, TypeGuard from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes32 @@ -481,6 +481,23 @@ class SetCodeTransaction: """ +AccessListCapableTransaction = ( + AccessListTransaction + | FeeMarketTransaction + | BlobTransaction + | SetCodeTransaction +) +""" +Transaction types that include an [EIP-2930]-style access list. + +See [`has_access_list`][hal] and [`Access`][a] for more details. + +[EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 +[hal]: ref:ethereum.forks.amsterdam.transactions.has_access_list +[a]: ref:ethereum.forks.amsterdam.transactions.Access +""" + + def encode_transaction(tx: Transaction) -> LegacyTransaction | Bytes: """ Encode a transaction into its RLP or typed transaction format. @@ -615,15 +632,7 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: create_cost = Uint(0) access_list_cost = Uint(0) - if isinstance( - tx, - ( - AccessListTransaction, - FeeMarketTransaction, - BlobTransaction, - SetCodeTransaction, - ), - ): + if has_access_list(tx): for access in tx.access_list: access_list_cost += GAS_TX_ACCESS_LIST_ADDRESS access_list_cost += ( @@ -884,3 +893,17 @@ def get_transaction_hash(tx: Bytes | LegacyTransaction) -> Hash32: return keccak256(rlp.encode(tx)) else: return keccak256(tx) + + +def has_access_list( + tx: Transaction, +) -> TypeGuard[AccessListCapableTransaction]: + """ + Return whether the transaction has an [EIP-2930]-style access list. + + [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 + """ + return isinstance( + tx, + AccessListCapableTransaction, + ) diff --git a/src/ethereum/forks/bpo5/fork.py b/src/ethereum/forks/bpo5/fork.py index 3dfd35f98a8..7e6b5ee46de 100644 --- a/src/ethereum/forks/bpo5/fork.py +++ b/src/ethereum/forks/bpo5/fork.py @@ -63,7 +63,6 @@ state_root, ) from .transactions import ( - AccessListTransaction, BlobTransaction, FeeMarketTransaction, LegacyTransaction, @@ -72,6 +71,7 @@ decode_transaction, encode_transaction, get_transaction_hash, + has_access_list, recover_sender, validate_transaction, ) @@ -916,15 +916,7 @@ def process_transaction( access_list_addresses = set() access_list_storage_keys = set() access_list_addresses.add(block_env.coinbase) - if isinstance( - tx, - ( - AccessListTransaction, - FeeMarketTransaction, - BlobTransaction, - SetCodeTransaction, - ), - ): + if has_access_list(tx): for access in tx.access_list: access_list_addresses.add(access.account) for slot in access.slots: diff --git a/src/ethereum/forks/bpo5/transactions.py b/src/ethereum/forks/bpo5/transactions.py index 048852529e4..c8f1826324e 100644 --- a/src/ethereum/forks/bpo5/transactions.py +++ b/src/ethereum/forks/bpo5/transactions.py @@ -5,7 +5,7 @@ """ from dataclasses import dataclass -from typing import Tuple +from typing import Tuple, TypeGuard from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes32 @@ -481,6 +481,23 @@ class SetCodeTransaction: """ +AccessListCapableTransaction = ( + AccessListTransaction + | FeeMarketTransaction + | BlobTransaction + | SetCodeTransaction +) +""" +Transaction types that include an [EIP-2930]-style access list. + +See [`has_access_list`][hal] and [`Access`][a] for more details. + +[EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 +[hal]: ref:ethereum.forks.amsterdam.transactions.has_access_list +[a]: ref:ethereum.forks.amsterdam.transactions.Access +""" + + def encode_transaction(tx: Transaction) -> LegacyTransaction | Bytes: """ Encode a transaction into its RLP or typed transaction format. @@ -615,15 +632,7 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: create_cost = Uint(0) access_list_cost = Uint(0) - if isinstance( - tx, - ( - AccessListTransaction, - FeeMarketTransaction, - BlobTransaction, - SetCodeTransaction, - ), - ): + if has_access_list(tx): for access in tx.access_list: access_list_cost += GAS_TX_ACCESS_LIST_ADDRESS access_list_cost += ( @@ -884,3 +893,17 @@ def get_transaction_hash(tx: Bytes | LegacyTransaction) -> Hash32: return keccak256(rlp.encode(tx)) else: return keccak256(tx) + + +def has_access_list( + tx: Transaction, +) -> TypeGuard[AccessListCapableTransaction]: + """ + Return whether the transaction has an [EIP-2930]-style access list. + + [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 + """ + return isinstance( + tx, + AccessListCapableTransaction, + ) diff --git a/src/ethereum/forks/osaka/fork.py b/src/ethereum/forks/osaka/fork.py index 3dfd35f98a8..7e6b5ee46de 100644 --- a/src/ethereum/forks/osaka/fork.py +++ b/src/ethereum/forks/osaka/fork.py @@ -63,7 +63,6 @@ state_root, ) from .transactions import ( - AccessListTransaction, BlobTransaction, FeeMarketTransaction, LegacyTransaction, @@ -72,6 +71,7 @@ decode_transaction, encode_transaction, get_transaction_hash, + has_access_list, recover_sender, validate_transaction, ) @@ -916,15 +916,7 @@ def process_transaction( access_list_addresses = set() access_list_storage_keys = set() access_list_addresses.add(block_env.coinbase) - if isinstance( - tx, - ( - AccessListTransaction, - FeeMarketTransaction, - BlobTransaction, - SetCodeTransaction, - ), - ): + if has_access_list(tx): for access in tx.access_list: access_list_addresses.add(access.account) for slot in access.slots: diff --git a/src/ethereum/forks/osaka/transactions.py b/src/ethereum/forks/osaka/transactions.py index 048852529e4..c8f1826324e 100644 --- a/src/ethereum/forks/osaka/transactions.py +++ b/src/ethereum/forks/osaka/transactions.py @@ -5,7 +5,7 @@ """ from dataclasses import dataclass -from typing import Tuple +from typing import Tuple, TypeGuard from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes32 @@ -481,6 +481,23 @@ class SetCodeTransaction: """ +AccessListCapableTransaction = ( + AccessListTransaction + | FeeMarketTransaction + | BlobTransaction + | SetCodeTransaction +) +""" +Transaction types that include an [EIP-2930]-style access list. + +See [`has_access_list`][hal] and [`Access`][a] for more details. + +[EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 +[hal]: ref:ethereum.forks.amsterdam.transactions.has_access_list +[a]: ref:ethereum.forks.amsterdam.transactions.Access +""" + + def encode_transaction(tx: Transaction) -> LegacyTransaction | Bytes: """ Encode a transaction into its RLP or typed transaction format. @@ -615,15 +632,7 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: create_cost = Uint(0) access_list_cost = Uint(0) - if isinstance( - tx, - ( - AccessListTransaction, - FeeMarketTransaction, - BlobTransaction, - SetCodeTransaction, - ), - ): + if has_access_list(tx): for access in tx.access_list: access_list_cost += GAS_TX_ACCESS_LIST_ADDRESS access_list_cost += ( @@ -884,3 +893,17 @@ def get_transaction_hash(tx: Bytes | LegacyTransaction) -> Hash32: return keccak256(rlp.encode(tx)) else: return keccak256(tx) + + +def has_access_list( + tx: Transaction, +) -> TypeGuard[AccessListCapableTransaction]: + """ + Return whether the transaction has an [EIP-2930]-style access list. + + [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 + """ + return isinstance( + tx, + AccessListCapableTransaction, + ) diff --git a/src/ethereum/forks/prague/fork.py b/src/ethereum/forks/prague/fork.py index 9f934c036d4..b1189cfa75e 100644 --- a/src/ethereum/forks/prague/fork.py +++ b/src/ethereum/forks/prague/fork.py @@ -62,7 +62,6 @@ state_root, ) from .transactions import ( - AccessListTransaction, BlobTransaction, FeeMarketTransaction, LegacyTransaction, @@ -71,6 +70,7 @@ decode_transaction, encode_transaction, get_transaction_hash, + has_access_list, recover_sender, validate_transaction, ) @@ -899,15 +899,7 @@ def process_transaction( access_list_addresses = set() access_list_storage_keys = set() access_list_addresses.add(block_env.coinbase) - if isinstance( - tx, - ( - AccessListTransaction, - FeeMarketTransaction, - BlobTransaction, - SetCodeTransaction, - ), - ): + if has_access_list(tx): for access in tx.access_list: access_list_addresses.add(access.account) for slot in access.slots: diff --git a/src/ethereum/forks/prague/transactions.py b/src/ethereum/forks/prague/transactions.py index 31a2b441616..485a2460ba4 100644 --- a/src/ethereum/forks/prague/transactions.py +++ b/src/ethereum/forks/prague/transactions.py @@ -5,7 +5,7 @@ """ from dataclasses import dataclass -from typing import Tuple +from typing import Tuple, TypeGuard from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes32 @@ -475,6 +475,23 @@ class SetCodeTransaction: """ +AccessListCapableTransaction = ( + AccessListTransaction + | FeeMarketTransaction + | BlobTransaction + | SetCodeTransaction +) +""" +Transaction types that include an [EIP-2930]-style access list. + +See [`has_access_list`][hal] and [`Access`][a] for more details. + +[EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 +[hal]: ref:ethereum.forks.amsterdam.transactions.has_access_list +[a]: ref:ethereum.forks.amsterdam.transactions.Access +""" + + def encode_transaction(tx: Transaction) -> LegacyTransaction | Bytes: """ Encode a transaction into its RLP or typed transaction format. @@ -607,15 +624,7 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: create_cost = Uint(0) access_list_cost = Uint(0) - if isinstance( - tx, - ( - AccessListTransaction, - FeeMarketTransaction, - BlobTransaction, - SetCodeTransaction, - ), - ): + if has_access_list(tx): for access in tx.access_list: access_list_cost += GAS_TX_ACCESS_LIST_ADDRESS access_list_cost += ( @@ -876,3 +885,17 @@ def get_transaction_hash(tx: Bytes | LegacyTransaction) -> Hash32: return keccak256(rlp.encode(tx)) else: return keccak256(tx) + + +def has_access_list( + tx: Transaction, +) -> TypeGuard[AccessListCapableTransaction]: + """ + Return whether the transaction has an [EIP-2930]-style access list. + + [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 + """ + return isinstance( + tx, + AccessListCapableTransaction, + ) diff --git a/tests/amsterdam/eip7928_block_level_access_lists/spec.py b/tests/amsterdam/eip7928_block_level_access_lists/spec.py index e008536563e..89a73baad7a 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/spec.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/spec.py @@ -13,7 +13,7 @@ class ReferenceSpec: ref_spec_7928 = ReferenceSpec( git_path="EIPS/eip-7928.md", - version="e7f0963a024b3d0dedc4488a7553bf7c70dedb3e", + version="aca88aa0932580c29d0233f902cb4390e88b8c41", ) diff --git a/tests/benchmark/compute/eip7928_block_level_access_lists/test_block_access_list.py b/tests/benchmark/compute/eip7928_block_level_access_lists/test_block_access_list.py index 7f18deabdc4..7c5c5e0c41f 100644 --- a/tests/benchmark/compute/eip7928_block_level_access_lists/test_block_access_list.py +++ b/tests/benchmark/compute/eip7928_block_level_access_lists/test_block_access_list.py @@ -33,10 +33,8 @@ from ethereum.crypto.hash import keccak256 -# TODO: Due to directory name this is required, link this to the -# corresponding reference in amsterdam tests? -REFERENCE_SPEC_GIT_PATH = "DUMMY/BAL.md" -REFERENCE_SPEC_VERSION = "1.0" +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-7928.md" +REFERENCE_SPEC_VERSION = "aca88aa0932580c29d0233f902cb4390e88b8c41" pytestmark = pytest.mark.valid_from("Amsterdam") diff --git a/tests/benchmark/stateful/eip7928_block_level_access_lists/__init__.py b/tests/benchmark/stateful/eip7928_block_level_access_lists/__init__.py new file mode 100644 index 00000000000..d02b97d33f6 --- /dev/null +++ b/tests/benchmark/stateful/eip7928_block_level_access_lists/__init__.py @@ -0,0 +1 @@ +"""EIP-7928 Block-level Access List benchmark tests.""" diff --git a/tests/benchmark/stateful/eip7928_block_level_access_lists/helpers.py b/tests/benchmark/stateful/eip7928_block_level_access_lists/helpers.py new file mode 100644 index 00000000000..2df7350c857 --- /dev/null +++ b/tests/benchmark/stateful/eip7928_block_level_access_lists/helpers.py @@ -0,0 +1,279 @@ +""" +Shared helpers for EIP-7928 BAL benchmark tests. + +Contracts use a gas-check loop: ``GAS > threshold`` at the top of +each iteration exits when remaining gas is too low for another +iteration plus teardown. This avoids pre-calculating iteration +counts and lets the last transaction naturally do fewer iterations. +""" + +from __future__ import annotations + +from dataclasses import dataclass + +from execution_testing import ( + Account, + Address, + Alloc, + BalAccountExpectation, + BalNonceChange, + BalStorageSlot, + BenchmarkTestFiller, + Block, + BlockAccessListExpectation, + Bytecode, + Fork, + Op, + Storage, + TestPhaseManager, + Transaction, +) + +CURSOR_SLOT = 0 +CURSOR_INIT = 1 + + +def cursor_read() -> Bytecode: + """PUSH1(CURSOR_SLOT) + SLOAD → stack: [..., cursor].""" + return Op.PUSH1(CURSOR_SLOT) + Op.SLOAD + + +def cursor_write() -> Bytecode: + """ + PUSH1(CURSOR_SLOT) + SSTORE ← stack: [..., cursor]. + + SSTORE metadata reflects runtime: cursor slot is warm + (setup SLOADs it) and nonzero-to-nonzero. + """ + return Op.PUSH1(CURSOR_SLOT) + Op.SSTORE( + key_warm=True, + original_value=1, + current_value=1, + new_value=2, + ) + + +def default_teardown() -> Bytecode: + """Standard loop teardown: write cursor and stop.""" + return Op.JUMPDEST + cursor_write() + Op.STOP + + +def sload_loop_body() -> Bytecode: + """SLOAD(cursor) then cursor++ (result discarded).""" + return Op.DUP1 + Op.SLOAD + Op.POP + Op.PUSH1(0x01) + Op.ADD + + +def sload_loop_body_reverse() -> Bytecode: + """SLOAD(cursor) then cursor-- (result discarded).""" + return Op.DUP1 + Op.SLOAD + Op.POP + Op.PUSH1(0x01) + Op.SWAP1 + Op.SUB + + +# -- Gas-check loop components -- # +# Used by both gas_check_loop_contract (assembly) and plan_benchmark +# (gas estimation). Operand values are irrelevant for gas costs. + + +def _pre_gas_header(gas_threshold: int = 0) -> Bytecode: + """Loop header prefix consumed before GAS reports.""" + return Op.JUMPDEST + Op.PUSH3(gas_threshold) + Op.GAS + + +def _loop_header(gas_threshold: int = 0) -> Bytecode: + """Full loop condition: JUMPDEST PUSH3 GAS GT ISZERO.""" + return _pre_gas_header(gas_threshold) + Op.GT + Op.ISZERO + + +def _loop_exit(target: int = 0) -> Bytecode: + """Exit jump when loop condition fails.""" + return Op.PUSH2(target) + Op.JUMPI + + +def _loop_back(target: int = 0) -> Bytecode: + """Back-edge jump to loop start.""" + return Op.PUSH2(target) + Op.JUMP + + +def gas_check_loop_contract( + setup: Bytecode, + body: Bytecode, + gas_threshold: int, + teardown: Bytecode | None = None, +) -> Bytecode: + """ + Assemble a contract with a gas-check loop. + + Structure: setup | JUMPDEST GAS>threshold? body JUMP | teardown. + The loop exits when remaining gas is too low for another + iteration plus teardown. + """ + if teardown is None: + teardown = default_teardown() + + loop_start = len(setup) + header = _loop_header(gas_threshold) + loop_end = ( + loop_start + + len(header) + + len(_loop_exit()) + + len(body) + + len(_loop_back()) + ) + + return ( + setup + + header + + _loop_exit(loop_end) + + body + + _loop_back(loop_start) + + teardown + ) + + +@dataclass(frozen=True) +class BenchmarkPlan: + """Pre-computed plan for a gas-check-loop benchmark.""" + + gas_limits: list[int] + iterations_per_tx: list[int] + total_iterations: int + gas_threshold: int + + +def plan_benchmark( + fork: Fork, + loop_body_gas: int, + setup_gas: int, + gas_benchmark_value: int, + teardown: Bytecode | None = None, + num_transactions: int | None = None, + tx_gas_limit: int | None = None, +) -> BenchmarkPlan: + """ + Plan transactions for a gas-check-loop benchmark. + + Fill up to *gas_benchmark_value* total gas with transactions. + The last transaction gets whatever gas remains. Pass *teardown* + when it differs from ``default_teardown()``. + """ + if teardown is None: + teardown = default_teardown() + + overhead = ( + _loop_header().gas_cost(fork) + + _loop_exit().gas_cost(fork) + + _loop_back().gas_cost(fork) + ) + teardown_gas = teardown.gas_cost(fork) + gas_opcode_offset = _pre_gas_header().gas_cost(fork) + + gas_threshold = loop_body_gas + overhead + teardown_gas + iteration_gas = loop_body_gas + overhead + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + min_useful = ( + intrinsic_gas + setup_gas + gas_threshold + gas_opcode_offset + 1 + ) + gas_limits: list[int] = [] + # Build per-tx gas limits. + if num_transactions is not None and tx_gas_limit is not None: + gas_limits = [tx_gas_limit] * num_transactions + else: + max_tx_gas = fork.transaction_gas_limit_cap() + assert max_tx_gas is not None + remaining = gas_benchmark_value + while remaining >= min_useful: + g = min(remaining, max_tx_gas) + if g < min_useful: + break + gas_limits.append(g) + remaining -= g + + # Expected iterations per tx. + def _iters(tx_gas: int) -> int: + avail = tx_gas - intrinsic_gas - setup_gas + if avail <= gas_threshold + gas_opcode_offset: + return 0 + return ( + avail - gas_threshold - gas_opcode_offset - 1 + ) // iteration_gas + 1 + + iters = [_iters(g) for g in gas_limits] + return BenchmarkPlan( + gas_limits=gas_limits, + iterations_per_tx=iters, + total_iterations=sum(iters), + gas_threshold=gas_threshold, + ) + + +def run_bal_benchmark( + pre: Alloc, + benchmark_test: BenchmarkTestFiller, + contract_code: Bytecode, + contract_storage: Storage, + plan: BenchmarkPlan, + data_slot_reads: list[int] | None = None, + extra_expectations: (dict[Address, BalAccountExpectation] | None) = None, +) -> None: + """Deploy contract, create txs, BAL expectations, and run.""" + contract = pre.deploy_contract( + code=contract_code, storage=contract_storage + ) + + num_txs = len(plan.gas_limits) + with TestPhaseManager.execution(): + sender = pre.fund_eoa() + transactions = [ + Transaction( + sender=sender, + to=contract, + gas_limit=plan.gas_limits[i], + data=b"", + ) + for i in range(num_txs) + ] + + # BAL expectations: contract slots + sender nonces. + # Use validate_any_change for cursor — exact values depend + # on gas dynamics and are verified by consensus test suites. + # All txs share a single sender to prevent trivial per-sender + # optimizations in BAL implementations. + expectations: dict[Address, BalAccountExpectation] = { + contract: BalAccountExpectation( + storage_reads=sorted(set(data_slot_reads or [])), + storage_changes=[ + BalStorageSlot( + slot=CURSOR_SLOT, + validate_any_change=True, + ) + ], + ), + sender: BalAccountExpectation( + nonce_changes=[ + BalNonceChange( + block_access_index=tx_idx + 1, + post_nonce=tx_idx + 1, + ) + for tx_idx in range(num_txs) + ], + ), + } + if extra_expectations: + expectations.update(extra_expectations) + + block = Block( + txs=transactions, + expected_block_access_list=BlockAccessListExpectation( + account_expectations=expectations + ), + ) + + # Post-state: only check sender nonce (sanity). + # Exact storage values depend on gas dynamics and may be + # slightly off; consensus correctness is verified elsewhere. + post: dict[Address, Account] = { + sender: Account(nonce=num_txs), + } + + benchmark_test( + pre=pre, post=post, blocks=[block], skip_gas_used_validation=True + ) diff --git a/tests/benchmark/stateful/eip7928_block_level_access_lists/spec.py b/tests/benchmark/stateful/eip7928_block_level_access_lists/spec.py new file mode 100644 index 00000000000..9f7c707d189 --- /dev/null +++ b/tests/benchmark/stateful/eip7928_block_level_access_lists/spec.py @@ -0,0 +1,21 @@ +""" +Reference spec for EIP-7928: Block-level Access Lists. + +https://eips.ethereum.org/EIPS/eip-7928 +""" + +from dataclasses import dataclass + + +@dataclass(frozen=True) +class ReferenceSpec: + """Reference specification.""" + + git_path: str + version: str + + +ref_spec_7928 = ReferenceSpec( + git_path="EIPS/eip-7928.md", + version="aca88aa0932580c29d0233f902cb4390e88b8c41", +) diff --git a/tests/benchmark/stateful/eip7928_block_level_access_lists/test_block_access_lists_compute_then_sload.py b/tests/benchmark/stateful/eip7928_block_level_access_lists/test_block_access_lists_compute_then_sload.py new file mode 100644 index 00000000000..05b23b3d156 --- /dev/null +++ b/tests/benchmark/stateful/eip7928_block_level_access_lists/test_block_access_lists_compute_then_sload.py @@ -0,0 +1,101 @@ +""" +Tests for EIP-7928 BAL with mixed computation and SLOADs. + +Each loop iteration performs N compute steps (pure arithmetic) +followed by one SLOAD + cursor increment. The ``compute_percent`` +parameter controls N so that approximately that fraction of each +iteration's gas is spent on computation vs. storage reads. +""" + +import pytest +from execution_testing import ( + Alloc, + BenchmarkTestFiller, + Bytecode, + Fork, + Op, + Storage, +) + +from .helpers import ( + cursor_read, + gas_check_loop_contract, + plan_benchmark, + run_bal_benchmark, + sload_loop_body, +) +from .spec import ref_spec_7928 + +REFERENCE_SPEC_GIT_PATH = ref_spec_7928.git_path +REFERENCE_SPEC_VERSION = ref_spec_7928.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +def _compute_step() -> Bytecode: + """ + One pure-compute step (stack unchanged). + + Uses cursor on stack: DUP, multiply by 3, add 7, discard. + """ + return Op.DUP1 + Op.PUSH1(0x03) + Op.MUL + Op.PUSH1(0x07) + Op.ADD + Op.POP + + +def _compute_steps_for_percent( + fork: Fork, + compute_percent: int, +) -> int: + """Return N compute steps per SLOAD for a target gas ratio.""" + sload_gas = sload_loop_body().gas_cost(fork) + step_gas = _compute_step().gas_cost(fork) + # N = pct * sload_gas / (step_gas * (100 - pct)) + n = compute_percent * sload_gas / (step_gas * (100 - compute_percent)) + return max(1, round(n)) + + +def _mixed_body(compute_steps: int) -> Bytecode: + """N compute steps then SLOAD(cursor) + cursor++.""" + step = _compute_step() + body = step + for _ in range(compute_steps - 1): + body += step + return body + sload_loop_body() + + +@pytest.mark.parametrize( + "compute_percent", + [5, 10, 25, 50], + ids=lambda p: f"compute_{p}pct", +) +def test_bal_compute_then_sload( + pre: Alloc, + benchmark_test: BenchmarkTestFiller, + fork: Fork, + gas_benchmark_value: int, + compute_percent: int, +) -> None: + """Test BAL with mixed computation and SLOAD per iteration.""" + n = _compute_steps_for_percent(fork, compute_percent) + body = _mixed_body(n) + plan = plan_benchmark( + fork, + loop_body_gas=body.gas_cost(fork), + setup_gas=cursor_read().gas_cost(fork), + gas_benchmark_value=gas_benchmark_value, + ) + total = plan.total_iterations + storage = Storage( + {i: i + 1 for i in range(total + 1)} # type: ignore + ) + run_bal_benchmark( + pre=pre, + benchmark_test=benchmark_test, + contract_code=gas_check_loop_contract( + setup=cursor_read(), + body=body, + gas_threshold=plan.gas_threshold, + ), + contract_storage=storage, + plan=plan, + data_slot_reads=list(range(1, total + 1)), + ) diff --git a/tests/benchmark/stateful/eip7928_block_level_access_lists/test_block_access_lists_max_accounts.py b/tests/benchmark/stateful/eip7928_block_level_access_lists/test_block_access_lists_max_accounts.py new file mode 100644 index 00000000000..6e2a9427c5e --- /dev/null +++ b/tests/benchmark/stateful/eip7928_block_level_access_lists/test_block_access_lists_max_accounts.py @@ -0,0 +1,113 @@ +""" +Tests for EIP-7928 BAL with maximum unique account accesses. + +Deploys a contract that reads its starting offset from CURSOR_SLOT, +then calls BALANCE on sequential addresses while remaining gas +exceeds a threshold. Each transaction writes an updated cursor, +creating inter-transaction dependencies that require the BAL. +""" + +import pytest +from execution_testing import ( + Address, + Alloc, + BalAccountExpectation, + BenchmarkTestFiller, + Bytecode, + Fork, + Op, + Storage, +) +from execution_testing.base_types.base_types import HashInt + +from .helpers import ( + CURSOR_INIT, + CURSOR_SLOT, + cursor_read, + cursor_write, + gas_check_loop_contract, + plan_benchmark, + run_bal_benchmark, +) +from .spec import ref_spec_7928 + +REFERENCE_SPEC_GIT_PATH = ref_spec_7928.git_path +REFERENCE_SPEC_VERSION = ref_spec_7928.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + +# Start addresses above precompiles and system contracts. +BASE_ADDR = 0x10000 + + +def _balance_body() -> Bytecode: + """ + BALANCE loop body. + + Stack on entry: [addr] + Stack on exit: [addr+1] + """ + return Op.DUP1 + Op.BALANCE + Op.POP + Op.PUSH1(0x01) + Op.ADD + + +def _teardown() -> Bytecode: + """Teardown: convert addr back to cursor, write, stop.""" + return ( + Op.JUMPDEST + + Op.PUSH3(BASE_ADDR) + + Op.SWAP1 + + Op.SUB + + cursor_write() + + Op.STOP + ) + + +def create_balance_loop_contract( + gas_threshold: int, +) -> Bytecode: + """ + Create contract that calls BALANCE on sequential addresses. + + 1. cursor = SLOAD(CURSOR_SLOT) + 2. addr = BASE_ADDR + cursor + 3. Loop while GAS > threshold: BALANCE(addr); addr++ + 4. SSTORE(CURSOR_SLOT, addr - BASE_ADDR) + """ + setup = cursor_read() + Op.PUSH3(BASE_ADDR) + Op.ADD + return gas_check_loop_contract( + setup=setup, + body=_balance_body(), + gas_threshold=gas_threshold, + teardown=_teardown(), + ) + + +def test_bal_max_account_access( + pre: Alloc, + benchmark_test: BenchmarkTestFiller, + fork: Fork, + gas_benchmark_value: int, +) -> None: + """Test BAL with maximum unique account accesses via BALANCE.""" + setup = cursor_read() + Op.PUSH3(BASE_ADDR) + Op.ADD + body_gas = _balance_body().gas_cost(fork) + plan = plan_benchmark( + fork, + loop_body_gas=body_gas, + setup_gas=setup.gas_cost(fork), + gas_benchmark_value=gas_benchmark_value, + teardown=_teardown(), + ) + total = plan.total_iterations + extra = { + Address(BASE_ADDR + i): BalAccountExpectation.empty() + for i in range(CURSOR_INIT, total + CURSOR_INIT) + } + run_bal_benchmark( + pre=pre, + benchmark_test=benchmark_test, + contract_code=create_balance_loop_contract(plan.gas_threshold), + contract_storage=Storage({HashInt(CURSOR_SLOT): HashInt(CURSOR_INIT)}), + plan=plan, + extra_expectations=extra, + ) diff --git a/tests/benchmark/stateful/eip7928_block_level_access_lists/test_block_access_lists_max_sloads.py b/tests/benchmark/stateful/eip7928_block_level_access_lists/test_block_access_lists_max_sloads.py new file mode 100644 index 00000000000..be0b8dbb85d --- /dev/null +++ b/tests/benchmark/stateful/eip7928_block_level_access_lists/test_block_access_lists_max_sloads.py @@ -0,0 +1,96 @@ +""" +Tests for EIP-7928 BAL with maximum SLOAD transactions. + +Deploys a loop-based contract that reads its starting cursor from +storage, then SLOADs sequential slots until remaining gas drops +below a threshold. The updated cursor is written back, creating +inter-transaction dependencies that require the BAL for parallel +execution. + +Parametrized over direction: forward (ascending slots) and reverse +(descending slots) to prevent direction-specific optimizations. +""" + +import pytest +from execution_testing import ( + Alloc, + BenchmarkTestFiller, + Bytecode, + Fork, + Storage, +) + +from .helpers import ( + cursor_read, + gas_check_loop_contract, + plan_benchmark, + run_bal_benchmark, + sload_loop_body, + sload_loop_body_reverse, +) +from .spec import ref_spec_7928 + +REFERENCE_SPEC_GIT_PATH = ref_spec_7928.git_path +REFERENCE_SPEC_VERSION = ref_spec_7928.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +def create_sload_loop_contract( + gas_threshold: int, + reverse: bool = False, +) -> Bytecode: + """ + Create contract that SLOADs sequential slots via cursor. + + 1. cursor = SLOAD(CURSOR_SLOT) + 2. Loop while GAS > threshold: + SLOAD(cursor); cursor += 1 (forward) or -= 1 (reverse) + 3. SSTORE(CURSOR_SLOT, cursor) + """ + body = sload_loop_body_reverse() if reverse else sload_loop_body() + return gas_check_loop_contract( + setup=cursor_read(), + body=body, + gas_threshold=gas_threshold, + ) + + +@pytest.mark.parametrize( + "reverse", + [False, True], + ids=["forward", "reverse"], +) +def test_bal_max_sloads( + pre: Alloc, + benchmark_test: BenchmarkTestFiller, + fork: Fork, + gas_benchmark_value: int, + reverse: bool, +) -> None: + """Test BAL with maximum sequential SLOADs via cursor.""" + body = sload_loop_body_reverse() if reverse else sload_loop_body() + body_gas = body.gas_cost(fork) + plan = plan_benchmark( + fork, + loop_body_gas=body_gas, + setup_gas=cursor_read().gas_cost(fork), + gas_benchmark_value=gas_benchmark_value, + ) + total = plan.total_iterations + # Cursor starts at slot 0; forward reads slots 1..total, + # reverse reads slots total..1. + cursor_start = total if reverse else 1 + storage = Storage( + {0: cursor_start} | {i: i for i in range(1, total + 1)} # type: ignore + ) + run_bal_benchmark( + pre=pre, + benchmark_test=benchmark_test, + contract_code=create_sload_loop_contract( + plan.gas_threshold, reverse=reverse + ), + contract_storage=storage, + plan=plan, + data_slot_reads=list(range(1, total + 1)), + ) diff --git a/tests/benchmark/stateful/eip7928_block_level_access_lists/test_block_access_lists_pointer_chase.py b/tests/benchmark/stateful/eip7928_block_level_access_lists/test_block_access_lists_pointer_chase.py new file mode 100644 index 00000000000..e40683db452 --- /dev/null +++ b/tests/benchmark/stateful/eip7928_block_level_access_lists/test_block_access_lists_pointer_chase.py @@ -0,0 +1,87 @@ +""" +Tests for EIP-7928 BAL with dependent pointer-chasing SLOADs. + +Deploys a contract with linked-list storage (slot[i] = i+1) that +reads its starting position from CURSOR_SLOT. Each transaction +follows the chain while remaining gas exceeds a threshold, then +writes the final chased value back to CURSOR_SLOT, creating +inter-transaction dependencies. +""" + +import pytest +from execution_testing import ( + Alloc, + BenchmarkTestFiller, + Bytecode, + Fork, + Op, + Storage, +) + +from .helpers import ( + cursor_read, + gas_check_loop_contract, + plan_benchmark, + run_bal_benchmark, +) +from .spec import ref_spec_7928 + +REFERENCE_SPEC_GIT_PATH = ref_spec_7928.git_path +REFERENCE_SPEC_VERSION = ref_spec_7928.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +def _chase_body() -> Bytecode: + """ + Pointer-chase loop body. + + Stack on entry: [cursor] + Stack on exit: [new_cursor] + """ + return Op.DUP1 + Op.SLOAD + Op.SWAP1 + Op.POP + + +def create_pointer_chase_contract( + gas_threshold: int, +) -> Bytecode: + """ + Create contract that follows a pointer chain via cursor. + + 1. cursor = SLOAD(CURSOR_SLOT) + 2. Loop while GAS > threshold: cursor = SLOAD(cursor) + 3. SSTORE(CURSOR_SLOT, cursor) + """ + return gas_check_loop_contract( + setup=cursor_read(), + body=_chase_body(), + gas_threshold=gas_threshold, + ) + + +def test_bal_max_pointer_chase( + pre: Alloc, + benchmark_test: BenchmarkTestFiller, + fork: Fork, + gas_benchmark_value: int, +) -> None: + """Test BAL with maximum dependent pointer-chasing SLOADs.""" + body_gas = _chase_body().gas_cost(fork) + plan = plan_benchmark( + fork, + loop_body_gas=body_gas, + setup_gas=cursor_read().gas_cost(fork), + gas_benchmark_value=gas_benchmark_value, + ) + total = plan.total_iterations + storage = Storage( + {i: i + 1 for i in range(total + 1)} # type: ignore + ) + run_bal_benchmark( + pre=pre, + benchmark_test=benchmark_test, + contract_code=create_pointer_chase_contract(plan.gas_threshold), + contract_storage=storage, + plan=plan, + data_slot_reads=list(range(1, total + 1)), + )