From b9da8eb037b538ca8702d19efb21803829054595 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Mon, 16 Dec 2024 15:38:29 +0000 Subject: [PATCH] fix(tests): EIP-4844, EIP-7691: Refactor EIP-4844 tests to use dynamic blob parameters --- tests/cancun/eip4788_beacon_root/conftest.py | 21 +- tests/cancun/eip4844_blobs/common.py | 47 +-- tests/cancun/eip4844_blobs/conftest.py | 270 +++++++++++- tests/cancun/eip4844_blobs/spec.py | 156 ++++--- tests/cancun/eip4844_blobs/test_blob_txs.py | 388 ++++++------------ .../eip4844_blobs/test_blob_txs_full.py | 101 ++--- .../eip4844_blobs/test_blobhash_opcode.py | 56 ++- .../test_blobhash_opcode_contexts.py | 64 ++- .../eip4844_blobs/test_excess_blob_gas.py | 288 +++++++------ .../test_excess_blob_gas_fork_transition.py | 15 +- .../eip7702_set_code_tx/test_set_code_txs.py | 3 +- 11 files changed, 756 insertions(+), 653 deletions(-) diff --git a/tests/cancun/eip4788_beacon_root/conftest.py b/tests/cancun/eip4788_beacon_root/conftest.py index c286bff0d1..4f06c608e5 100644 --- a/tests/cancun/eip4788_beacon_root/conftest.py +++ b/tests/cancun/eip4788_beacon_root/conftest.py @@ -7,20 +7,10 @@ import pytest -from ethereum_test_tools import ( - AccessList, - Account, - Address, - Alloc, - Bytecode, - Environment, - Hash, - Storage, - Transaction, - add_kzg_version, - keccak256, -) -from ethereum_test_tools.vm.opcode import Opcodes as Op +from ethereum_test_forks import Fork +from ethereum_test_tools import AccessList, Account, Address, Alloc, Bytecode, Environment, Hash +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools import Storage, Transaction, add_kzg_version, keccak256 from .spec import Spec, SpecHelpers @@ -232,6 +222,7 @@ def tx_type() -> int: @pytest.fixture def tx( pre: Alloc, + fork: Fork, tx_to_address: Address, tx_data: bytes, tx_type: int, @@ -254,7 +245,7 @@ def tx( kwargs["access_list"] = access_list if tx_type == 3: - kwargs["max_fee_per_blob_gas"] = 1 + kwargs["max_fee_per_blob_gas"] = fork.min_base_fee_per_blob_gas() kwargs["blob_versioned_hashes"] = add_kzg_version([0], BLOB_COMMITMENT_VERSION_KZG) if tx_type > 3: diff --git a/tests/cancun/eip4844_blobs/common.py b/tests/cancun/eip4844_blobs/common.py index a97b0edf60..a10d07a0ce 100644 --- a/tests/cancun/eip4844_blobs/common.py +++ b/tests/cancun/eip4844_blobs/common.py @@ -14,7 +14,7 @@ ) from ethereum_test_tools.vm.opcode import Opcodes as Op -from .spec import Spec, SpecHelpers +from .spec import Spec INF_POINT = (0xC0 << 376).to_bytes(48, byteorder="big") Z = 0x623CE31CF9759A5C8DAF3A357992F9F3DD7F9339D8998BC8E68373E54F00B75E @@ -57,12 +57,6 @@ def blobs_to_transaction_input( return (blobs, kzg_commitments, kzg_proofs) -# Simple list of blob versioned hashes ranging from bytes32(1 to 4) -simple_blob_hashes: list[bytes] = add_kzg_version( - [(1 << x) for x in range(SpecHelpers.max_blobs_per_block())], - Spec.BLOB_COMMITMENT_VERSION_KZG, -) - # Random fixed list of blob versioned hashes random_blob_hashes = add_kzg_version( [ @@ -284,7 +278,7 @@ class BlobhashScenario: """ @staticmethod - def create_blob_hashes_list(length: int) -> list[list[bytes]]: + def create_blob_hashes_list(length: int, max_blobs_per_block: int) -> list[list[bytes]]: """ Creates a list of MAX_BLOBS_PER_BLOCK blob hashes using `random_blob_hashes`. @@ -293,20 +287,20 @@ def create_blob_hashes_list(length: int) -> list[list[bytes]]: length: MAX_BLOBS_PER_BLOCK * length -> [0x01, 0x02, 0x03, 0x04, ..., 0x0A, 0x0B, 0x0C, 0x0D] - Then split list into smaller chunks of SpecHelpers.max_blobs_per_block() + Then split list into smaller chunks of max_blobs_per_block -> [[0x01, 0x02, 0x03, 0x04], ..., [0x0a, 0x0b, 0x0c, 0x0d]] """ b_hashes = [ random_blob_hashes[i % len(random_blob_hashes)] - for i in range(SpecHelpers.max_blobs_per_block() * length) + for i in range(max_blobs_per_block * length) ] return [ - b_hashes[i : i + SpecHelpers.max_blobs_per_block()] - for i in range(0, len(b_hashes), SpecHelpers.max_blobs_per_block()) + b_hashes[i : i + max_blobs_per_block] + for i in range(0, len(b_hashes), max_blobs_per_block) ] @staticmethod - def blobhash_sstore(index: int): + def blobhash_sstore(index: int, max_blobs_per_block: int): """ Returns an BLOBHASH sstore to the given index. @@ -315,35 +309,38 @@ def blobhash_sstore(index: int): the BLOBHASH sstore. """ invalidity_check = Op.SSTORE(index, 0x01) - if index < 0 or index >= SpecHelpers.max_blobs_per_block(): + if index < 0 or index >= max_blobs_per_block: return invalidity_check + Op.SSTORE(index, Op.BLOBHASH(index)) return Op.SSTORE(index, Op.BLOBHASH(index)) @classmethod - def generate_blobhash_bytecode(cls, scenario_name: str) -> bytes: + def generate_blobhash_bytecode(cls, scenario_name: str, max_blobs_per_block: int) -> bytes: """ Returns BLOBHASH bytecode for the given scenario. """ scenarios = { "single_valid": sum( - cls.blobhash_sstore(i) for i in range(SpecHelpers.max_blobs_per_block()) + cls.blobhash_sstore(i, max_blobs_per_block) for i in range(max_blobs_per_block) ), "repeated_valid": sum( - sum(cls.blobhash_sstore(i) for _ in range(10)) - for i in range(SpecHelpers.max_blobs_per_block()) + sum(cls.blobhash_sstore(i, max_blobs_per_block) for _ in range(10)) + for i in range(max_blobs_per_block) ), "valid_invalid": sum( - cls.blobhash_sstore(i) - + cls.blobhash_sstore(SpecHelpers.max_blobs_per_block()) - + cls.blobhash_sstore(i) - for i in range(SpecHelpers.max_blobs_per_block()) + cls.blobhash_sstore(i, max_blobs_per_block) + + cls.blobhash_sstore(max_blobs_per_block, max_blobs_per_block) + + cls.blobhash_sstore(i, max_blobs_per_block) + for i in range(max_blobs_per_block) ), "varied_valid": sum( - cls.blobhash_sstore(i) + cls.blobhash_sstore(i + 1) + cls.blobhash_sstore(i) - for i in range(SpecHelpers.max_blobs_per_block() - 1) + cls.blobhash_sstore(i, max_blobs_per_block) + + cls.blobhash_sstore(i + 1, max_blobs_per_block) + + cls.blobhash_sstore(i, max_blobs_per_block) + for i in range(max_blobs_per_block - 1) ), "invalid_calls": sum( - cls.blobhash_sstore(i) for i in range(-5, SpecHelpers.max_blobs_per_block() + 5) + cls.blobhash_sstore(i, max_blobs_per_block) + for i in range(-5, max_blobs_per_block + 5) ), } scenario = scenarios.get(scenario_name) diff --git a/tests/cancun/eip4844_blobs/conftest.py b/tests/cancun/eip4844_blobs/conftest.py index e8c6c5505d..e8f3cfdfce 100644 --- a/tests/cancun/eip4844_blobs/conftest.py +++ b/tests/cancun/eip4844_blobs/conftest.py @@ -3,17 +3,268 @@ """ import pytest -from ethereum_test_tools import Alloc, Block, Hash, Transaction, add_kzg_version +from ethereum_test_forks import Fork +from ethereum_test_tools import Alloc, Block, Environment, Hash, Transaction, add_kzg_version -from .spec import BlockHeaderBlobGasFields, Spec +from .spec import Spec + + +@pytest.fixture +def block_base_fee_per_gas() -> int: + """Default max fee per gas for transactions sent during test.""" + return 7 + + +@pytest.fixture +def target_blobs_per_block(fork: Fork) -> int: + """ + Default number of blobs to be included in the block. + """ + return fork.target_blobs_per_block() + + +@pytest.fixture +def max_blobs_per_block(fork: Fork) -> int: + """ + Default number of blobs to be included in the block. + """ + return fork.max_blobs_per_block() + + +@pytest.fixture +def blob_gas_per_blob(fork: Fork) -> int: + """Default blob gas cost per blob.""" + return fork.blob_gas_per_blob() + + +@pytest.fixture(autouse=True) +def parent_excess_blobs() -> int | None: + """ + Default excess blobs of the parent block. + + Can be overloaded by a test case to provide a custom parent excess blob + count. + """ + return 10 # Defaults to a blob gas price of 1. + + +@pytest.fixture(autouse=True) +def parent_blobs() -> int | None: + """ + Default data blobs of the parent blob. + + Can be overloaded by a test case to provide a custom parent blob count. + """ + return 0 + + +@pytest.fixture +def parent_excess_blob_gas( + parent_excess_blobs: int | None, + blob_gas_per_blob: int, +) -> int | None: + """ + Calculates the excess blob gas of the parent block from the excess blobs. + """ + if parent_excess_blobs is None: + return None + assert parent_excess_blobs >= 0 + return parent_excess_blobs * blob_gas_per_blob + + +@pytest.fixture +def excess_blob_gas( + fork: Fork, + parent_excess_blobs: int | None, + parent_blobs: int | None, +) -> int | None: + """ + Calculates the excess blob gas of the block under test from the parent block. + + Value can be overloaded by a test case to provide a custom excess blob gas. + """ + if parent_excess_blobs is None or parent_blobs is None: + return None + excess_blob_gas = fork.excess_blob_gas_calculator() + return excess_blob_gas( + parent_excess_blobs=parent_excess_blobs, + parent_blob_count=parent_blobs, + ) + + +@pytest.fixture +def correct_excess_blob_gas( + fork: Fork, + parent_excess_blobs: int | None, + parent_blobs: int | None, +) -> int: + """ + Calculates the correct excess blob gas of the block under test from the parent block. + + Should not be overloaded by a test case. + """ + if parent_excess_blobs is None or parent_blobs is None: + return 0 + excess_blob_gas = fork.excess_blob_gas_calculator() + return excess_blob_gas( + parent_excess_blobs=parent_excess_blobs, + parent_blob_count=parent_blobs, + ) + + +@pytest.fixture +def block_fee_per_blob_gas( # noqa: D103 + fork: Fork, + correct_excess_blob_gas: int, +) -> int: + get_blob_gas_price = fork.blob_gas_price_calculator() + return get_blob_gas_price(excess_blob_gas=correct_excess_blob_gas) + + +@pytest.fixture +def blob_gas_price( + fork: Fork, + excess_blob_gas: int | None, +) -> int | None: + """ + Blob gas price for the block of the test. + """ + if excess_blob_gas is None: + return None + + get_blob_gas_price = fork.blob_gas_price_calculator() + return get_blob_gas_price( + excess_blob_gas=excess_blob_gas, + ) + + +@pytest.fixture +def genesis_excess_blob_gas( + parent_excess_blob_gas: int | None, + parent_blobs: int, + target_blobs_per_block: int, + blob_gas_per_blob: int, +) -> int: + """ + Default excess blob gas for the genesis block. + """ + excess_blob_gas = parent_excess_blob_gas if parent_excess_blob_gas else 0 + if parent_blobs: + # We increase the excess blob gas of the genesis because + # we cannot include blobs in the genesis, so the + # test blobs are actually in block 1. + excess_blob_gas += target_blobs_per_block * blob_gas_per_blob + return excess_blob_gas + + +@pytest.fixture +def env( + block_base_fee_per_gas: int, + genesis_excess_blob_gas: int, +) -> Environment: + """ + Prepare the environment of the genesis block for all blockchain tests. + """ + return Environment( + excess_blob_gas=genesis_excess_blob_gas, + blob_gas_used=0, + base_fee_per_gas=block_base_fee_per_gas, + ) + + +@pytest.fixture +def tx_value() -> int: + """ + Default value contained by the transactions sent during test. + + Can be overloaded by a test case to provide a custom transaction value. + """ + return 1 + + +@pytest.fixture +def tx_calldata() -> bytes: + """Default calldata in transactions sent during test.""" + return b"" + + +@pytest.fixture(autouse=True) +def tx_max_fee_per_gas( + block_base_fee_per_gas: int, +) -> int: + """ + Max fee per gas value used by all transactions sent during test. + + By default the max fee per gas is the same as the block fee per gas. + + Can be overloaded by a test case to test rejection of transactions where + the max fee per gas is insufficient. + """ + return block_base_fee_per_gas + + +@pytest.fixture +def tx_max_priority_fee_per_gas() -> int: + """ + Default max priority fee per gas for transactions sent during test. + + Can be overloaded by a test case to provide a custom max priority fee per + gas. + """ + return 0 + + +@pytest.fixture +def tx_max_fee_per_blob_gas_multiplier() -> int: + """ + Default max fee per blob gas multiplier for transactions sent during test. + + Can be overloaded by a test case to provide a custom max fee per blob gas + multiplier. + """ + return 1 + + +@pytest.fixture +def tx_max_fee_per_blob_gas_delta() -> int: + """ + Default max fee per blob gas delta for transactions sent during test. + + Can be overloaded by a test case to provide a custom max fee per blob gas + delta. + """ + return 0 + + +@pytest.fixture +def tx_max_fee_per_blob_gas( # noqa: D103 + blob_gas_price: int | None, + tx_max_fee_per_blob_gas_multiplier: int, + tx_max_fee_per_blob_gas_delta: int, +) -> int: + """ + Default max fee per blob gas for transactions sent during test. + + By default, it is set to the blob gas price of the block. + + Can be overloaded by a test case to test rejection of transactions where + the max fee per blob gas is insufficient. + """ + if blob_gas_price is None: + # When fork transitioning, the default blob gas price is 1. + return 1 + return (blob_gas_price * tx_max_fee_per_blob_gas_multiplier) + tx_max_fee_per_blob_gas_delta @pytest.fixture def non_zero_blob_gas_used_genesis_block( pre: Alloc, parent_blobs: int, + fork: Fork, + genesis_excess_blob_gas: int, parent_excess_blob_gas: int, tx_max_fee_per_gas: int, + target_blobs_per_block: int, ) -> Block | None: """ For test cases with a non-zero blobGasUsed field in the @@ -32,16 +283,19 @@ def non_zero_blob_gas_used_genesis_block( if parent_blobs == 0: return None - parent_excess_blob_gas += Spec.TARGET_BLOB_GAS_PER_BLOCK - excess_blob_gas = Spec.calc_excess_blob_gas( - BlockHeaderBlobGasFields(parent_excess_blob_gas, 0) - ) + excess_blob_gas_calculator = fork.excess_blob_gas_calculator(block_number=1) + assert parent_excess_blob_gas == excess_blob_gas_calculator( + parent_excess_blob_gas=genesis_excess_blob_gas, + parent_blob_count=0, + ), "parent excess blob gas is not as expected for extra block" sender = pre.fund_eoa(10**27) # Address that contains no code, nor balance and is not a contract. empty_account_destination = pre.fund_eoa(0) + blob_gas_price_calculator = fork.blob_gas_price_calculator(block_number=1) + return Block( txs=[ Transaction( @@ -52,7 +306,9 @@ def non_zero_blob_gas_used_genesis_block( gas_limit=21_000, max_fee_per_gas=tx_max_fee_per_gas, max_priority_fee_per_gas=0, - max_fee_per_blob_gas=Spec.get_blob_gasprice(excess_blob_gas=excess_blob_gas), + max_fee_per_blob_gas=blob_gas_price_calculator( + excess_blob_gas=parent_excess_blob_gas + ), access_list=[], blob_versioned_hashes=add_kzg_version( [Hash(x) for x in range(parent_blobs)], diff --git a/tests/cancun/eip4844_blobs/spec.py b/tests/cancun/eip4844_blobs/spec.py index c253aee0a2..0f298a39bf 100644 --- a/tests/cancun/eip4844_blobs/spec.py +++ b/tests/cancun/eip4844_blobs/spec.py @@ -1,10 +1,12 @@ """ Defines EIP-4844 specification constants and functions. """ +import itertools from dataclasses import dataclass from hashlib import sha256 -from typing import Optional +from typing import List, Optional, Tuple +from ethereum_test_forks import Fork from ethereum_test_tools import Transaction @@ -21,16 +23,6 @@ class ReferenceSpec: ref_spec_4844 = ReferenceSpec("EIPS/eip-4844.md", "f0eb6a364aaf5ccb43516fa2c269a54fb881ecfd") -@dataclass(frozen=True) -class BlockHeaderBlobGasFields: - """ - A helper class for the blob gas fields in a block header. - """ - - excess_blob_gas: int - blob_gas_used: int - - # Constants @dataclass(frozen=True) class Spec: @@ -48,17 +40,12 @@ class Spec: BLOB_COMMITMENT_VERSION_KZG = 1 POINT_EVALUATION_PRECOMPILE_ADDRESS = 10 POINT_EVALUATION_PRECOMPILE_GAS = 50_000 - MAX_BLOB_GAS_PER_BLOCK = 786432 - TARGET_BLOB_GAS_PER_BLOCK = 393216 - MIN_BLOB_GASPRICE = 1 - BLOB_GASPRICE_UPDATE_FRACTION = 3338477 # MAX_VERSIONED_HASHES_LIST_SIZE = 2**24 # MAX_CALLDATA_SIZE = 2**24 # MAX_ACCESS_LIST_SIZE = 2**24 # MAX_ACCESS_LIST_STORAGE_KEYS = 2**24 # MAX_TX_WRAP_COMMITMENTS = 2**12 # LIMIT_BLOBS_PER_TX = 2**12 - GAS_PER_BLOB = 2**17 HASH_OPCODE_BYTE = 0x49 HASH_GAS_COST = 3 @@ -80,49 +67,13 @@ def kzg_to_versioned_hash( return blob_commitment_version_kzg + sha256(kzg_commitment).digest()[1:] @classmethod - def fake_exponential(cls, factor: int, numerator: int, denominator: int) -> int: - """ - Used to calculate the blob gas cost. - """ - i = 1 - output = 0 - numerator_accumulator = factor * denominator - while numerator_accumulator > 0: - output += numerator_accumulator - numerator_accumulator = (numerator_accumulator * numerator) // (denominator * i) - i += 1 - return output // denominator - - @classmethod - def calc_excess_blob_gas(cls, parent: BlockHeaderBlobGasFields) -> int: - """ - Calculate the excess blob gas for a block given the excess blob gas - and blob gas used from the parent block header. - """ - if parent.excess_blob_gas + parent.blob_gas_used < cls.TARGET_BLOB_GAS_PER_BLOCK: - return 0 - else: - return parent.excess_blob_gas + parent.blob_gas_used - cls.TARGET_BLOB_GAS_PER_BLOCK - - @classmethod - def get_total_blob_gas(cls, tx: Transaction) -> int: + def get_total_blob_gas(cls, *, tx: Transaction, blob_gas_per_blob: int) -> int: """ Calculate the total blob gas for a transaction. """ if tx.blob_versioned_hashes is None: return 0 - return cls.GAS_PER_BLOB * len(tx.blob_versioned_hashes) - - @classmethod - def get_blob_gasprice(cls, *, excess_blob_gas: int) -> int: - """ - Calculate the blob gas price from the excess. - """ - return cls.fake_exponential( - cls.MIN_BLOB_GASPRICE, - excess_blob_gas, - cls.BLOB_GASPRICE_UPDATE_FRACTION, - ) + return blob_gas_per_blob * len(tx.blob_versioned_hashes) @dataclass(frozen=True) @@ -135,49 +86,86 @@ class SpecHelpers: BYTES_PER_FIELD_ELEMENT = 32 @classmethod - def max_blobs_per_block(cls) -> int: # MAX_BLOBS_PER_BLOCK = - """ - Returns the maximum number of blobs per block. - """ - return Spec.MAX_BLOB_GAS_PER_BLOCK // Spec.GAS_PER_BLOB - - @classmethod - def target_blobs_per_block(cls) -> int: + def get_min_excess_blob_gas_for_blob_gas_price( + cls, + *, + fork: Fork, + blob_gas_price: int, + ) -> int: """ - Returns the target number of blobs per block. + Gets the minimum required excess blob gas value to get a given blob gas cost in a block """ - return Spec.TARGET_BLOB_GAS_PER_BLOCK // Spec.GAS_PER_BLOB + current_excess_blob_gas = 0 + current_blob_gas_price = 1 + get_blob_gas_price = fork.blob_gas_price_calculator() + gas_per_blob = fork.blob_gas_per_blob() + while current_blob_gas_price < blob_gas_price: + current_excess_blob_gas += gas_per_blob + current_blob_gas_price = get_blob_gas_price(excess_blob_gas=current_excess_blob_gas) + return current_excess_blob_gas @classmethod - def calc_excess_blob_gas_from_blob_count( - cls, parent_excess_blob_gas: int, parent_blob_count: int + def get_min_excess_blobs_for_blob_gas_price( + cls, + *, + fork: Fork, + blob_gas_price: int, ) -> int: """ - Calculate the excess blob gas for a block given the parent excess blob gas - and the number of blobs in the block. + Gets the minimum required excess blobs to get a given blob gas cost in a block """ - parent_consumed_blob_gas = parent_blob_count * Spec.GAS_PER_BLOB - return Spec.calc_excess_blob_gas( - BlockHeaderBlobGasFields(parent_excess_blob_gas, parent_consumed_blob_gas) + gas_per_blob = fork.blob_gas_per_blob() + return ( + cls.get_min_excess_blob_gas_for_blob_gas_price( + fork=fork, + blob_gas_price=blob_gas_price, + ) + // gas_per_blob ) @classmethod - def get_min_excess_blob_gas_for_blob_gas_price(cls, blob_gas_price: int) -> int: + def get_blob_combinations( + cls, + blob_count: int, + ) -> List[Tuple[int, ...]]: + """ + Get all possible combinations of blobs that result in a given blob count. + """ + all = [ + seq + for i in range( + blob_count + 1, 0, -1 + ) # We can have from 1 to at most MAX_BLOBS_PER_BLOCK blobs per block + for seq in itertools.combinations_with_replacement( + range(1, blob_count + 2), i + ) # We iterate through all possible combinations + if sum(seq) == blob_count # And we only keep the ones that match the + # expected invalid blob count + ] + + # We also add the reversed version of each combination, only if it's not + # already in the list. E.g. (4, 1) is added from (1, 4) but not + # (1, 1, 1, 1, 1) because its reversed version is identical. + all += [tuple(reversed(x)) for x in all if tuple(reversed(x)) not in all] + return all + + @classmethod + def all_valid_blob_combinations(cls, fork: Fork) -> List[Tuple[int, ...]]: """ - Gets the minimum required excess blob gas value to get a given blob gas cost in a block + Returns all valid blob tx combinations for a given block, + assuming the given MAX_BLOBS_PER_BLOCK """ - current_excess_blob_gas = 0 - current_blob_gas_price = 1 - while current_blob_gas_price < blob_gas_price: - current_excess_blob_gas += Spec.GAS_PER_BLOB - current_blob_gas_price = Spec.get_blob_gasprice( - excess_blob_gas=current_excess_blob_gas - ) - return current_excess_blob_gas + max_blobs_per_block = fork.max_blobs_per_block() + all: List[Tuple[int, ...]] = [] + for i in range(1, max_blobs_per_block + 1): + all += cls.get_blob_combinations(i) + return all @classmethod - def get_min_excess_blobs_for_blob_gas_price(cls, blob_gas_price: int) -> int: + def invalid_blob_combinations(cls, fork: Fork) -> List[Tuple[int, ...]]: """ - Gets the minimum required excess blobs to get a given blob gas cost in a block + Returns invalid blob tx combinations for a given block that use up to + MAX_BLOBS_PER_BLOCK+1 blobs """ - return cls.get_min_excess_blob_gas_for_blob_gas_price(blob_gas_price) // Spec.GAS_PER_BLOB + max_blobs_per_block = fork.max_blobs_per_block() + return cls.get_blob_combinations(max_blobs_per_block + 1) diff --git a/tests/cancun/eip4844_blobs/test_blob_txs.py b/tests/cancun/eip4844_blobs/test_blob_txs.py index 1e1047c6e8..b521cef9cc 100644 --- a/tests/cancun/eip4844_blobs/test_blob_txs.py +++ b/tests/cancun/eip4844_blobs/test_blob_txs.py @@ -15,7 +15,6 @@ """ # noqa: E501 -import itertools from typing import List, Optional, Tuple import pytest @@ -45,6 +44,7 @@ TransactionException, add_kzg_version, ) +from pytest_plugins import fork_covariant_parametrize from .spec import Spec, SpecHelpers, ref_spec_4844 @@ -77,16 +77,6 @@ def destination_account( return pre.fund_eoa(destination_account_balance) -@pytest.fixture -def tx_value() -> int: - """ - Default value contained by the transactions sent during test. - - Can be overloaded by a test case to provide a custom transaction value. - """ - return 1 - - @pytest.fixture def tx_gas( fork: Fork, @@ -98,81 +88,6 @@ def tx_gas( return tx_intrinsic_cost_calculator(calldata=tx_calldata, access_list=tx_access_list) -@pytest.fixture -def tx_calldata() -> bytes: - """Default calldata in transactions sent during test.""" - return b"" - - -@pytest.fixture -def block_fee_per_gas() -> int: - """Default max fee per gas for transactions sent during test.""" - return 7 - - -@pytest.fixture(autouse=True) -def parent_excess_blobs() -> Optional[int]: - """ - Default excess blobs of the parent block. - - Can be overloaded by a test case to provide a custom parent excess blob - count. - """ - return 10 # Defaults to a blob gas price of 1. - - -@pytest.fixture(autouse=True) -def parent_blobs() -> Optional[int]: - """ - Default data blobs of the parent blob. - - Can be overloaded by a test case to provide a custom parent blob count. - """ - return 0 - - -@pytest.fixture -def parent_excess_blob_gas( - parent_excess_blobs: Optional[int], -) -> Optional[int]: - """ - Calculates the excess blob gas of the parent block from the excess blobs. - """ - if parent_excess_blobs is None: - return None - return parent_excess_blobs * Spec.GAS_PER_BLOB - - -@pytest.fixture -def blob_gasprice( - parent_excess_blob_gas: Optional[int], - parent_blobs: Optional[int], -) -> Optional[int]: - """ - Blob gas price for the block of the test. - """ - if parent_excess_blob_gas is None or parent_blobs is None: - return None - - return Spec.get_blob_gasprice( - excess_blob_gas=SpecHelpers.calc_excess_blob_gas_from_blob_count( - parent_excess_blob_gas=parent_excess_blob_gas, - parent_blob_count=parent_blobs, - ), - ) - - -@pytest.fixture -def tx_max_priority_fee_per_gas() -> int: - """ - Default max priority fee per gas for transactions sent during test. - - Can be overloaded by a test case to provide a custom max priority fee per - gas. - """ - return 0 - - @pytest.fixture def blobs_per_tx() -> List[int]: """ @@ -206,6 +121,7 @@ def blob_hashes_per_tx(blobs_per_tx: List[int]) -> List[List[bytes]]: @pytest.fixture def total_account_minimum_balance( # noqa: D103 + blob_gas_per_blob: int, tx_gas: int, tx_value: int, tx_max_fee_per_gas: int, @@ -218,7 +134,7 @@ def total_account_minimum_balance( # noqa: D103 """ minimum_cost = 0 for tx_blob_count in [len(x) for x in blob_hashes_per_tx]: - blob_cost = tx_max_fee_per_blob_gas * Spec.GAS_PER_BLOB * tx_blob_count + blob_cost = tx_max_fee_per_blob_gas * blob_gas_per_blob * tx_blob_count minimum_cost += (tx_gas * tx_max_fee_per_gas) + tx_value + blob_cost return minimum_cost @@ -227,8 +143,9 @@ def total_account_minimum_balance( # noqa: D103 def total_account_transactions_fee( # noqa: D103 tx_gas: int, tx_value: int, - blob_gasprice: int, - block_fee_per_gas: int, + blob_gas_price: int, + block_base_fee_per_gas: int, + blob_gas_per_blob: int, tx_max_fee_per_gas: int, tx_max_priority_fee_per_gas: int, blob_hashes_per_tx: List[List[bytes]], @@ -238,47 +155,16 @@ def total_account_transactions_fee( # noqa: D103 """ total_cost = 0 for tx_blob_count in [len(x) for x in blob_hashes_per_tx]: - blob_cost = blob_gasprice * Spec.GAS_PER_BLOB * tx_blob_count + blob_cost = blob_gas_price * blob_gas_per_blob * tx_blob_count block_producer_fee = ( - tx_max_fee_per_gas - block_fee_per_gas if tx_max_priority_fee_per_gas else 0 + tx_max_fee_per_gas - block_base_fee_per_gas if tx_max_priority_fee_per_gas else 0 + ) + total_cost += ( + (tx_gas * (block_base_fee_per_gas + block_producer_fee)) + tx_value + blob_cost ) - total_cost += (tx_gas * (block_fee_per_gas + block_producer_fee)) + tx_value + blob_cost return total_cost -@pytest.fixture(autouse=True) -def tx_max_fee_per_gas( - block_fee_per_gas: int, -) -> int: - """ - Max fee per gas value used by all transactions sent during test. - - By default the max fee per gas is the same as the block fee per gas. - - Can be overloaded by a test case to test rejection of transactions where - the max fee per gas is insufficient. - """ - return block_fee_per_gas - - -@pytest.fixture -def tx_max_fee_per_blob_gas( # noqa: D103 - blob_gasprice: Optional[int], -) -> int: - """ - Default max fee per blob gas for transactions sent during test. - - By default, it is set to the blob gas price of the block. - - Can be overloaded by a test case to test rejection of transactions where - the max fee per blob gas is insufficient. - """ - if blob_gasprice is None: - # When fork transitioning, the default blob gas price is 1. - return 1 - return blob_gasprice - - @pytest.fixture def tx_access_list() -> List[AccessList]: """ @@ -357,30 +243,9 @@ def account_balance_modifier() -> int: return 0 -@pytest.fixture -def env( - parent_excess_blob_gas: Optional[int], - parent_blobs: int, -) -> Environment: - """ - Prepare the environment of the genesis block for all blockchain tests. - """ - excess_blob_gas = parent_excess_blob_gas if parent_excess_blob_gas else 0 - if parent_blobs: - # We increase the excess blob gas of the genesis because - # we cannot include blobs in the genesis, so the - # test blobs are actually in block 1. - excess_blob_gas += Spec.TARGET_BLOB_GAS_PER_BLOCK - return Environment( - excess_blob_gas=excess_blob_gas, - blob_gas_used=0, - ) - - @pytest.fixture def state_env( - parent_excess_blob_gas: Optional[int], - parent_blobs: int, + excess_blob_gas: Optional[int], ) -> Environment: """ Prepare the environment for all state test cases. @@ -390,10 +255,7 @@ def state_env( is not decreased by the target. """ return Environment( - excess_blob_gas=SpecHelpers.calc_excess_blob_gas_from_blob_count( - parent_excess_blob_gas=parent_excess_blob_gas if parent_excess_blob_gas else 0, - parent_blob_count=parent_blobs, - ), + excess_blob_gas=excess_blob_gas if excess_blob_gas else 0, ) @@ -449,13 +311,17 @@ def expected_blob_gas_used( block_number=block_number, timestamp=block_timestamp ): return Header.EMPTY_FIELD - return sum([Spec.get_total_blob_gas(tx) for tx in txs]) + blob_gas_per_blob = fork.blob_gas_per_blob( + block_number=block_number, + timestamp=block_timestamp, + ) + return sum([Spec.get_total_blob_gas(tx=tx, blob_gas_per_blob=blob_gas_per_blob) for tx in txs]) @pytest.fixture def expected_excess_blob_gas( fork: Fork, - parent_excess_blob_gas: Optional[int], + parent_excess_blobs: Optional[int], parent_blobs: Optional[int], block_number: int, block_timestamp: int, @@ -467,8 +333,9 @@ def expected_excess_blob_gas( block_number=block_number, timestamp=block_timestamp ): return Header.EMPTY_FIELD - return SpecHelpers.calc_excess_blob_gas_from_blob_count( - parent_excess_blob_gas=parent_excess_blob_gas if parent_excess_blob_gas else 0, + excess_blob_gas = fork.excess_blob_gas_calculator() + return excess_blob_gas( + parent_excess_blobs=parent_excess_blobs if parent_excess_blobs else 0, parent_blob_count=parent_blobs if parent_blobs else 0, ) @@ -524,56 +391,9 @@ def block( ) -def all_valid_blob_combinations() -> List[Tuple[int, ...]]: - """ - Returns all valid blob tx combinations for a given block, - assuming the given MAX_BLOBS_PER_BLOCK - """ - all = [ - seq - for i in range( - SpecHelpers.max_blobs_per_block(), 0, -1 - ) # We can have from 1 to at most MAX_BLOBS_PER_BLOCK blobs per block - for seq in itertools.combinations_with_replacement( - range(1, SpecHelpers.max_blobs_per_block() + 1), i - ) # We iterate through all possible combinations - if sum(seq) - <= SpecHelpers.max_blobs_per_block() # And we only keep the ones that are valid - ] - # We also add the reversed version of each combination, only if it's not - # already in the list. E.g. (2, 1, 1) is added from (1, 1, 2) but not - # (1, 1, 1) because its reversed version is identical. - all += [tuple(reversed(x)) for x in all if tuple(reversed(x)) not in all] - return all - - -def invalid_blob_combinations() -> List[Tuple[int, ...]]: - """ - Returns invalid blob tx combinations for a given block that use up to - MAX_BLOBS_PER_BLOCK+1 blobs - """ - all = [ - seq - for i in range( - SpecHelpers.max_blobs_per_block() + 1, 0, -1 - ) # We can have from 1 to at most MAX_BLOBS_PER_BLOCK blobs per block - for seq in itertools.combinations_with_replacement( - range(1, SpecHelpers.max_blobs_per_block() + 2), i - ) # We iterate through all possible combinations - if sum(seq) - == SpecHelpers.max_blobs_per_block() + 1 # And we only keep the ones that match the - # expected invalid blob count - ] - # We also add the reversed version of each combination, only if it's not - # already in the list. E.g. (4, 1) is added from (1, 4) but not - # (1, 1, 1, 1, 1) because its reversed version is identical. - all += [tuple(reversed(x)) for x in all if tuple(reversed(x)) not in all] - return all - - -@pytest.mark.parametrize( - "blobs_per_tx", - all_valid_blob_combinations(), +@fork_covariant_parametrize( + parameter_names=["blobs_per_tx"], + fn=SpecHelpers.all_valid_blob_combinations, ) @pytest.mark.valid_from("Cancun") def test_valid_blob_tx_combinations( @@ -601,26 +421,70 @@ def test_valid_blob_tx_combinations( ) -@pytest.mark.parametrize( - "parent_excess_blobs,parent_blobs,tx_max_fee_per_blob_gas,tx_error", - [ - # tx max_blob_gas_cost of the transaction is not enough +def generate_invalid_tx_max_fee_per_blob_gas_tests( + fork: Fork, +) -> List: + """ + Returns a list of tests for invalid blob transactions due to insufficient max fee per blob gas + parametrized for each different fork. + """ + min_base_fee_per_blob_gas = fork.min_base_fee_per_blob_gas() + minimum_excess_blobs_for_first_increment = SpecHelpers.get_min_excess_blobs_for_blob_gas_price( + fork=fork, + blob_gas_price=min_base_fee_per_blob_gas + 1, + ) + next_base_fee_per_blob_gas = fork.blob_gas_price_calculator()( + excess_blob_gas=minimum_excess_blobs_for_first_increment, + ) + + tests = [] + tests.append( pytest.param( - SpecHelpers.get_min_excess_blobs_for_blob_gas_price(2) - 1, # blob gas price is 1 - SpecHelpers.target_blobs_per_block() + 1, # blob gas cost increases to 2 - 1, # tx max_blob_gas_cost is 1 + minimum_excess_blobs_for_first_increment - 1, # blob gas price is 1 + fork.target_blobs_per_block() + 1, # blob gas cost increases to above the minimum + min_base_fee_per_blob_gas, # tx max_blob_gas_cost is the minimum TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS, id="insufficient_max_fee_per_blob_gas", - ), - # tx max_blob_gas_cost of the transaction is zero, which is invalid + ) + ) + if (next_base_fee_per_blob_gas - min_base_fee_per_blob_gas) > 1: + tests.append( + pytest.param( + minimum_excess_blobs_for_first_increment + - 1, # blob gas price is one less than the minimum + fork.target_blobs_per_block() + 1, # blob gas cost increases to above the minimum + next_base_fee_per_blob_gas + - 1, # tx max_blob_gas_cost is one less than the minimum + TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS, + id="insufficient_max_fee_per_blob_gas_one_less_than_next", + ) + ) + if min_base_fee_per_blob_gas > 1: + tests.append( + pytest.param( + 0, # blob gas price is the minimum + 0, # blob gas cost stays put at 1 + min_base_fee_per_blob_gas - 1, # tx max_blob_gas_cost is one less than the minimum + TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS, + id="insufficient_max_fee_per_blob_gas_one_less_than_min", + ) + ) + + tests.append( pytest.param( - 0, # blob gas price is 1 + 0, # blob gas price is the minimum 0, # blob gas cost stays put at 1 0, # tx max_blob_gas_cost is 0 TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS, id="invalid_max_fee_per_blob_gas", - ), - ], + ) + ) + return tests + + +@fork_covariant_parametrize( + parameter_names="parent_excess_blobs,parent_blobs,tx_max_fee_per_blob_gas,tx_error", + fn=generate_invalid_tx_max_fee_per_blob_gas_tests, ) @pytest.mark.parametrize( "account_balance_modifier", @@ -652,26 +516,9 @@ def test_invalid_tx_max_fee_per_blob_gas( ) -@pytest.mark.parametrize( - "parent_excess_blobs,parent_blobs,tx_max_fee_per_blob_gas,tx_error", - [ - # tx max_blob_gas_cost of the transaction is not enough - pytest.param( - SpecHelpers.get_min_excess_blobs_for_blob_gas_price(2) - 1, # blob gas price is 1 - SpecHelpers.target_blobs_per_block() + 1, # blob gas cost increases to 2 - 1, # tx max_blob_gas_cost is 1 - TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS, - id="insufficient_max_fee_per_blob_gas", - ), - # tx max_blob_gas_cost of the transaction is zero, which is invalid - pytest.param( - 0, # blob gas price is 1 - 0, # blob gas cost stays put at 1 - 0, # tx max_blob_gas_cost is 0 - TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS, - id="invalid_max_fee_per_blob_gas", - ), - ], +@fork_covariant_parametrize( + parameter_names="parent_excess_blobs,parent_blobs,tx_max_fee_per_blob_gas,tx_error", + fn=generate_invalid_tx_max_fee_per_blob_gas_tests, ) @pytest.mark.valid_from("Cancun") def test_invalid_tx_max_fee_per_blob_gas_state( @@ -731,9 +578,9 @@ def test_invalid_normal_gas( ) -@pytest.mark.parametrize( - "blobs_per_tx", - invalid_blob_combinations(), +@fork_covariant_parametrize( + parameter_names="blobs_per_tx", + fn=SpecHelpers.invalid_blob_combinations, ) @pytest.mark.parametrize( "tx_error", [TransactionException.TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED], ids=[""] @@ -774,7 +621,7 @@ def test_invalid_block_blob_count( [b"", b"\x00", b"\x01"], ids=["no_calldata", "single_zero_calldata", "single_one_calldata"], ) -@pytest.mark.parametrize("tx_max_fee_per_blob_gas", [1, 100, 10000]) +@pytest.mark.parametrize("tx_max_fee_per_blob_gas_multiplier", [1, 100, 10000]) @pytest.mark.parametrize("account_balance_modifier", [-1], ids=["exact_balance_minus_1"]) @pytest.mark.parametrize("tx_error", [TransactionException.INSUFFICIENT_ACCOUNT_FUNDS], ids=[""]) @pytest.mark.valid_from("Cancun") @@ -816,7 +663,7 @@ def test_insufficient_balance_blob_tx( [b"", b"\x00", b"\x01"], ids=["no_calldata", "single_zero_calldata", "single_one_calldata"], ) -@pytest.mark.parametrize("tx_max_fee_per_blob_gas", [1, 100, 10000]) +@pytest.mark.parametrize("tx_max_fee_per_blob_gas_multiplier", [1, 100, 10000]) @pytest.mark.valid_from("Cancun") def test_sufficient_balance_blob_tx( state_test: StateTestFiller, @@ -856,7 +703,7 @@ def test_sufficient_balance_blob_tx( [b"", b"\x00", b"\x01"], ids=["no_calldata", "single_zero_calldata", "single_one_calldata"], ) -@pytest.mark.parametrize("tx_max_fee_per_blob_gas", [1, 100, 10000]) +@pytest.mark.parametrize("tx_max_fee_per_blob_gas_multiplier", [1, 100, 10000]) @pytest.mark.parametrize("sender_initial_balance", [0]) @pytest.mark.valid_from("Cancun") def test_sufficient_balance_blob_tx_pre_fund_tx( @@ -914,7 +761,7 @@ def test_sufficient_balance_blob_tx_pre_fund_tx( [b"", b"\x01"], ids=["no_calldata", "single_non_zero_byte_calldata"], ) -@pytest.mark.parametrize("tx_max_fee_per_blob_gas", [1, 100]) +@pytest.mark.parametrize("tx_max_fee_per_blob_gas_multiplier", [1, 100]) @pytest.mark.parametrize( "tx_gas", [500_000], ids=[""] ) # Increase gas to account for contract code @@ -970,9 +817,9 @@ def test_blob_gas_subtraction_tx( ) -@pytest.mark.parametrize( - "blobs_per_tx", - all_valid_blob_combinations(), +@fork_covariant_parametrize( + parameter_names="blobs_per_tx", + fn=SpecHelpers.all_valid_blob_combinations, ) @pytest.mark.parametrize("account_balance_modifier", [-1], ids=["exact_balance_minus_1"]) @pytest.mark.parametrize("tx_error", [TransactionException.INSUFFICIENT_ACCOUNT_FUNDS], ids=[""]) @@ -997,16 +844,29 @@ def test_insufficient_balance_blob_tx_combinations( ) -@pytest.mark.parametrize( - "blobs_per_tx,tx_error", - [ - ([0], TransactionException.TYPE_3_TX_ZERO_BLOBS), - ( - [SpecHelpers.max_blobs_per_block() + 1], +def generate_invalid_tx_blob_count_tests( + fork: Fork, +) -> List: + """ + Returns a list of tests for invalid blob transactions due to invalid blob counts. + """ + return [ + pytest.param( + [0], + TransactionException.TYPE_3_TX_ZERO_BLOBS, + id="too_few_blobs", + ), + pytest.param( + [fork.max_blobs_per_block() + 1], TransactionException.TYPE_3_TX_BLOB_COUNT_EXCEEDED, + id="too_many_blobs", ), - ], - ids=["too_few_blobs", "too_many_blobs"], + ] + + +@fork_covariant_parametrize( + parameter_names="blobs_per_tx,tx_error", + fn=generate_invalid_tx_blob_count_tests, ) @pytest.mark.valid_from("Cancun") def test_invalid_tx_blob_count( @@ -1169,9 +1029,9 @@ def test_invalid_blob_tx_contract_creation( ) -# ---------------------------------------- -# Opcode Tests in Blob Transaction Context -# ---------------------------------------- +# # ---------------------------------------- +# # Opcode Tests in Blob Transaction Context +# # ---------------------------------------- @pytest.fixture @@ -1179,7 +1039,7 @@ def opcode( request, sender: EOA, tx_calldata: bytes, - block_fee_per_gas: int, + block_base_fee_per_gas: int, tx_max_fee_per_gas: int, tx_max_priority_fee_per_gas: int, tx_value: int, @@ -1218,12 +1078,12 @@ def opcode( {0: tx_calldata.ljust(32, b"\x00")}, ) elif request.param == Op.GASPRICE: - assert tx_max_fee_per_gas >= block_fee_per_gas + assert tx_max_fee_per_gas >= block_base_fee_per_gas return ( Op.SSTORE(0, Op.GASPRICE), { - 0: min(tx_max_priority_fee_per_gas, tx_max_fee_per_gas - block_fee_per_gas) - + block_fee_per_gas + 0: min(tx_max_priority_fee_per_gas, tx_max_fee_per_gas - block_base_fee_per_gas) + + block_base_fee_per_gas }, ) raise Exception("Unknown opcode") @@ -1407,8 +1267,8 @@ def test_blob_tx_attribute_calldata_opcodes( @pytest.mark.parametrize("tx_max_priority_fee_per_gas", [0, 2]) # always below data fee -@pytest.mark.parametrize("tx_max_fee_per_blob_gas", [1, 3]) # normal and above priority fee -@pytest.mark.parametrize("tx_max_fee_per_gas", [100]) # always above priority fee +@pytest.mark.parametrize("tx_max_fee_per_blob_gas_delta", [0, 1]) # normal and above priority fee +@pytest.mark.parametrize("tx_max_fee_per_gas", [100]) # always above priority fee (FOR CANCUN) @pytest.mark.parametrize("opcode", [Op.GASPRICE], indirect=True) @pytest.mark.parametrize("tx_gas", [500_000]) @pytest.mark.valid_from("Cancun") diff --git a/tests/cancun/eip4844_blobs/test_blob_txs_full.py b/tests/cancun/eip4844_blobs/test_blob_txs_full.py index 0f27179c99..69ba9188b3 100644 --- a/tests/cancun/eip4844_blobs/test_blob_txs_full.py +++ b/tests/cancun/eip4844_blobs/test_blob_txs_full.py @@ -7,6 +7,7 @@ import pytest +from ethereum_test_forks import Fork from ethereum_test_tools import ( Address, Alloc, @@ -18,6 +19,7 @@ Transaction, TransactionException, ) +from pytest_plugins import fork_covariant_parametrize from .common import INF_POINT, Blob from .spec import Spec, SpecHelpers, ref_spec_4844 @@ -54,12 +56,6 @@ def tx_calldata() -> bytes: return b"" -@pytest.fixture -def block_fee_per_gas() -> int: - """Default max fee per gas for transactions sent during test.""" - return 7 - - @pytest.fixture(autouse=True) def parent_excess_blobs() -> int: """ @@ -81,32 +77,6 @@ def parent_blobs() -> int: return 0 -@pytest.fixture -def parent_excess_blob_gas( - parent_excess_blobs: int, -) -> int: - """ - Calculates the excess blob gas of the parent block from the excess blobs. - """ - return parent_excess_blobs * Spec.GAS_PER_BLOB - - -@pytest.fixture -def blob_gasprice( - parent_excess_blob_gas: int, - parent_blobs: int, -) -> int: - """ - Blob gas price for the block of the test. - """ - return Spec.get_blob_gasprice( - excess_blob_gas=SpecHelpers.calc_excess_blob_gas_from_blob_count( - parent_excess_blob_gas=parent_excess_blob_gas, - parent_blob_count=parent_blobs, - ), - ) - - @pytest.fixture def tx_max_priority_fee_per_gas() -> int: """ @@ -128,7 +98,7 @@ def txs_versioned_hashes(txs_blobs: List[List[Blob]]) -> List[List[bytes]]: @pytest.fixture(autouse=True) def tx_max_fee_per_gas( - block_fee_per_gas: int, + block_base_fee_per_gas: int, ) -> int: """ Max fee per gas value used by all transactions sent during test. @@ -138,12 +108,12 @@ def tx_max_fee_per_gas( Can be overloaded by a test case to test rejection of transactions where the max fee per gas is insufficient. """ - return block_fee_per_gas + return block_base_fee_per_gas @pytest.fixture def tx_max_fee_per_blob_gas( # noqa: D103 - blob_gasprice: Optional[int], + blob_gas_price: Optional[int], ) -> int: """ Default max fee per blob gas for transactions sent during test. @@ -153,10 +123,10 @@ def tx_max_fee_per_blob_gas( # noqa: D103 Can be overloaded by a test case to test rejection of transactions where the max fee per blob gas is insufficient. """ - if blob_gasprice is None: + if blob_gas_price is None: # When fork transitioning, the default blob gas price is 1. return 1 - return blob_gasprice + return blob_gas_price @pytest.fixture @@ -236,6 +206,7 @@ def env( def blocks( txs: List[Transaction], txs_wrapped_blobs: List[bool], + blob_gas_per_blob: int, ) -> List[Block]: """ Prepare the list of blocks for all test cases. @@ -258,7 +229,7 @@ def blocks( if tx.blob_versioned_hashes is not None ] ) - * Spec.GAS_PER_BLOB + * blob_gas_per_blob ) return [ Block( @@ -267,59 +238,63 @@ def blocks( ] -@pytest.mark.parametrize( - "txs_blobs,txs_wrapped_blobs", - [ - ( +def generate_full_blob_tests( + fork: Fork, +) -> List: + """ + Returns a list of tests for invalid blob transactions due to insufficient max fee per blob gas + parametrized for each different fork. + """ + blob_size = Spec.FIELD_ELEMENTS_PER_BLOB * SpecHelpers.BYTES_PER_FIELD_ELEMENT + max_blobs = fork.max_blobs_per_block() + return [ + pytest.param( [ # Txs [ # Blobs per transaction Blob( - blob=bytes( - Spec.FIELD_ELEMENTS_PER_BLOB * SpecHelpers.BYTES_PER_FIELD_ELEMENT - ), + blob=bytes(blob_size), kzg_commitment=INF_POINT, kzg_proof=INF_POINT, ), ] ], [True], + id="one_full_blob_one_tx", ), - ( + pytest.param( [ # Txs [ # Blobs per transaction Blob( - blob=bytes( - Spec.FIELD_ELEMENTS_PER_BLOB * SpecHelpers.BYTES_PER_FIELD_ELEMENT - ), + blob=bytes(blob_size), kzg_commitment=INF_POINT, kzg_proof=INF_POINT, ) ] - for _ in range(SpecHelpers.max_blobs_per_block()) + for _ in range(max_blobs) ], - [True] + ([False] * (SpecHelpers.max_blobs_per_block() - 1)), + [True] + ([False] * (max_blobs - 1)), + id="one_full_blob_max_txs", ), - ( + pytest.param( [ # Txs [ # Blobs per transaction Blob( - blob=bytes( - Spec.FIELD_ELEMENTS_PER_BLOB * SpecHelpers.BYTES_PER_FIELD_ELEMENT - ), + blob=bytes(blob_size), kzg_commitment=INF_POINT, kzg_proof=INF_POINT, ) ] - for _ in range(SpecHelpers.max_blobs_per_block()) + for _ in range(max_blobs) ], - ([False] * (SpecHelpers.max_blobs_per_block() - 1)) + [True], + ([False] * (max_blobs - 1)) + [True], + id="one_full_blob_at_the_end_max_txs", ), - ], - ids=[ - "one_full_blob_one_tx", - "one_full_blob_max_txs", - "one_full_blob_at_the_end_max_txs", - ], + ] + + +@fork_covariant_parametrize( + parameter_names="txs_blobs,txs_wrapped_blobs", + fn=generate_full_blob_tests, ) @pytest.mark.valid_from("Cancun") def test_reject_valid_full_blob_in_block_rlp( diff --git a/tests/cancun/eip4844_blobs/test_blobhash_opcode.py b/tests/cancun/eip4844_blobs/test_blobhash_opcode.py index f09abada60..ea91846f10 100644 --- a/tests/cancun/eip4844_blobs/test_blobhash_opcode.py +++ b/tests/cancun/eip4844_blobs/test_blobhash_opcode.py @@ -20,6 +20,7 @@ import pytest +from ethereum_test_forks import Fork from ethereum_test_tools import ( Account, Address, @@ -35,7 +36,7 @@ from ethereum_test_tools.vm.opcode import Opcodes as Op from .common import BlobhashScenario, random_blob_hashes -from .spec import Spec, SpecHelpers, ref_spec_4844 +from .spec import Spec, ref_spec_4844 REFERENCE_SPEC_GIT_PATH = ref_spec_4844.git_path REFERENCE_SPEC_VERSION = ref_spec_4844.version @@ -59,9 +60,11 @@ @pytest.mark.with_all_tx_types def test_blobhash_gas_cost( pre: Alloc, + fork: Fork, tx_type: int, blobhash_index: int, state_test: StateTestFiller, + target_blobs_per_block: int, ): """ Tests `BLOBHASH` opcode gas cost using a variety of indexes. @@ -89,8 +92,8 @@ def test_blobhash_gas_cost( access_list=[] if tx_type >= 1 else None, max_fee_per_gas=10 if tx_type >= 2 else None, max_priority_fee_per_gas=10 if tx_type >= 2 else None, - max_fee_per_blob_gas=10 if tx_type == 3 else None, - blob_versioned_hashes=random_blob_hashes[0 : SpecHelpers.target_blobs_per_block()] + max_fee_per_blob_gas=(fork.min_base_fee_per_blob_gas() * 10) if tx_type == 3 else None, + blob_versioned_hashes=random_blob_hashes[0:target_blobs_per_block] if tx_type == 3 else None, ) @@ -115,8 +118,10 @@ def test_blobhash_gas_cost( ) def test_blobhash_scenarios( pre: Alloc, + fork: Fork, scenario: str, blockchain_test: BlockchainTestFiller, + max_blobs_per_block: int, ): """ Tests that the `BLOBHASH` opcode returns the correct versioned hash for @@ -126,8 +131,12 @@ def test_blobhash_scenarios( the valid range `[0, 2**256-1]`. """ TOTAL_BLOCKS = 5 - b_hashes_list = BlobhashScenario.create_blob_hashes_list(length=TOTAL_BLOCKS) - blobhash_calls = BlobhashScenario.generate_blobhash_bytecode(scenario) + b_hashes_list = BlobhashScenario.create_blob_hashes_list( + length=TOTAL_BLOCKS, max_blobs_per_block=max_blobs_per_block + ) + blobhash_calls = BlobhashScenario.generate_blobhash_bytecode( + scenario_name=scenario, max_blobs_per_block=max_blobs_per_block + ) sender = pre.fund_eoa() blocks: List[Block] = [] @@ -146,17 +155,14 @@ def test_blobhash_scenarios( access_list=[], max_fee_per_gas=10, max_priority_fee_per_gas=10, - max_fee_per_blob_gas=10, + max_fee_per_blob_gas=(fork.min_base_fee_per_blob_gas() * 10), blob_versioned_hashes=b_hashes_list[i], ) ] ) ) post[address] = Account( - storage={ - index: b_hashes_list[i][index] - for index in range(SpecHelpers.max_blobs_per_block()) - } + storage={index: b_hashes_list[i][index] for index in range(max_blobs_per_block)} ) blockchain_test( pre=pre, @@ -173,8 +179,10 @@ def test_blobhash_scenarios( ) def test_blobhash_invalid_blob_index( pre: Alloc, + fork: Fork, blockchain_test: BlockchainTestFiller, - scenario, + scenario: str, + max_blobs_per_block: int, ): """ Tests that the `BLOBHASH` opcode returns a zeroed `bytes32` value for invalid @@ -187,13 +195,15 @@ def test_blobhash_invalid_blob_index( It confirms that the returned value is a zeroed `bytes32` for each case. """ TOTAL_BLOCKS = 5 - blobhash_calls = BlobhashScenario.generate_blobhash_bytecode(scenario) + blobhash_calls = BlobhashScenario.generate_blobhash_bytecode( + scenario_name=scenario, max_blobs_per_block=max_blobs_per_block + ) sender = pre.fund_eoa() blocks: List[Block] = [] post = {} for i in range(TOTAL_BLOCKS): address = pre.deploy_contract(blobhash_calls) - blob_per_block = (i % SpecHelpers.max_blobs_per_block()) + 1 + blob_per_block = (i % max_blobs_per_block) + 1 blobs = [random_blob_hashes[blob] for blob in range(blob_per_block)] blocks.append( Block( @@ -207,7 +217,7 @@ def test_blobhash_invalid_blob_index( access_list=[], max_fee_per_gas=10, max_priority_fee_per_gas=10, - max_fee_per_blob_gas=10, + max_fee_per_blob_gas=(fork.min_base_fee_per_blob_gas() * 10), blob_versioned_hashes=blobs, ) ] @@ -218,7 +228,7 @@ def test_blobhash_invalid_blob_index( index: (0 if index < 0 or index >= blob_per_block else blobs[index]) for index in range( -TOTAL_BLOCKS, - blob_per_block + (TOTAL_BLOCKS - (i % SpecHelpers.max_blobs_per_block())), + blob_per_block + (TOTAL_BLOCKS - (i % max_blobs_per_block)), ) } ) @@ -231,7 +241,9 @@ def test_blobhash_invalid_blob_index( def test_blobhash_multiple_txs_in_block( pre: Alloc, + fork: Fork, blockchain_test: BlockchainTestFiller, + max_blobs_per_block: int, ): """ Tests that the `BLOBHASH` opcode returns the appropriate values when there @@ -240,7 +252,9 @@ def test_blobhash_multiple_txs_in_block( Scenarios involve tx type 3 followed by tx type 2 running the same code within a block, including the opposite. """ - blobhash_bytecode = BlobhashScenario.generate_blobhash_bytecode("single_valid") + blobhash_bytecode = BlobhashScenario.generate_blobhash_bytecode( + scenario_name="single_valid", max_blobs_per_block=max_blobs_per_block + ) addresses = [pre.deploy_contract(blobhash_bytecode) for _ in range(4)] sender = pre.fund_eoa() @@ -255,10 +269,8 @@ def blob_tx(address: Address, type: int): access_list=[] if type >= 1 else None, max_fee_per_gas=10, max_priority_fee_per_gas=10, - max_fee_per_blob_gas=10 if type >= 3 else None, - blob_versioned_hashes=random_blob_hashes[0 : SpecHelpers.max_blobs_per_block()] - if type >= 3 - else None, + max_fee_per_blob_gas=(fork.min_base_fee_per_blob_gas() * 10) if type >= 3 else None, + blob_versioned_hashes=random_blob_hashes[0:max_blobs_per_block] if type >= 3 else None, ) blocks = [ @@ -283,10 +295,10 @@ def blob_tx(address: Address, type: int): ] post = { Address(address): Account( - storage={i: random_blob_hashes[i] for i in range(SpecHelpers.max_blobs_per_block())} + storage={i: random_blob_hashes[i] for i in range(max_blobs_per_block)} ) if address in (addresses[1], addresses[3]) - else Account(storage={i: 0 for i in range(SpecHelpers.max_blobs_per_block())}) + else Account(storage={i: 0 for i in range(max_blobs_per_block)}) for address in addresses } blockchain_test( diff --git a/tests/cancun/eip4844_blobs/test_blobhash_opcode_contexts.py b/tests/cancun/eip4844_blobs/test_blobhash_opcode_contexts.py index ba59dc2be8..16c1c2ccd2 100644 --- a/tests/cancun/eip4844_blobs/test_blobhash_opcode_contexts.py +++ b/tests/cancun/eip4844_blobs/test_blobhash_opcode_contexts.py @@ -5,8 +5,11 @@ """ # noqa: E501 +from typing import List + import pytest +from ethereum_test_forks import Fork from ethereum_test_tools import ( Account, Block, @@ -15,10 +18,11 @@ TestAddress, Transaction, YulCompiler, + add_kzg_version, ) -from .common import BlobhashContext, simple_blob_hashes -from .spec import Spec, SpecHelpers, ref_spec_4844 +from .common import BlobhashContext +from .spec import Spec, ref_spec_4844 REFERENCE_SPEC_GIT_PATH = ref_spec_4844.git_path REFERENCE_SPEC_VERSION = ref_spec_4844.version @@ -26,19 +30,6 @@ pytestmark = pytest.mark.valid_from("Cancun") -# Blob transaction template -tx_type_3 = Transaction( - ty=Spec.BLOB_TX_TYPE, - data=Hash(0), - gas_limit=3000000, - max_fee_per_gas=10, - max_priority_fee_per_gas=10, - max_fee_per_blob_gas=10, - access_list=[], - blob_versioned_hashes=simple_blob_hashes, -) - - def create_opcode_context(pre, tx, post): """ Generates an opcode context based on the key provided by the @@ -51,6 +42,35 @@ def create_opcode_context(pre, tx, post): } +@pytest.fixture() +def simple_blob_hashes( + max_blobs_per_block: int, +) -> List[bytes]: + """Simple list of blob versioned hashes ranging from bytes32(1 to 4)""" + return add_kzg_version( + [(1 << x) for x in range(max_blobs_per_block)], + Spec.BLOB_COMMITMENT_VERSION_KZG, + ) + + +@pytest.fixture() +def tx_type_3( + fork: Fork, + simple_blob_hashes: List[bytes], +) -> Transaction: + """Blob transaction template.""" + return Transaction( + ty=Spec.BLOB_TX_TYPE, + data=Hash(0), + gas_limit=3000000, + max_fee_per_gas=10, + max_priority_fee_per_gas=10, + max_fee_per_blob_gas=fork.min_base_fee_per_blob_gas() * 10, + access_list=[], + blob_versioned_hashes=simple_blob_hashes, + ) + + @pytest.fixture( params=[ "on_top_level_call_stack", @@ -66,7 +86,13 @@ def create_opcode_context(pre, tx, post): "on_type_0_tx", ] ) -def opcode_context(yul: YulCompiler, request): +def opcode_context( + yul: YulCompiler, + request, + max_blobs_per_block: int, + simple_blob_hashes: List[bytes], + tx_type_3: Transaction, +): """ Fixture that is parameterized by each BLOBHASH opcode test case in order to return the corresponding constructed opcode context. @@ -137,7 +163,7 @@ def opcode_context(yul: YulCompiler, request): ), }, tx_type_3.copy( - data=Hash(0) + Hash(SpecHelpers.max_blobs_per_block() - 1), + data=Hash(0) + Hash(max_blobs_per_block - 1), to=BlobhashContext.address("delegatecall"), ), { @@ -159,7 +185,7 @@ def opcode_context(yul: YulCompiler, request): ), }, tx_type_3.copy( - data=Hash(0) + Hash(SpecHelpers.max_blobs_per_block() - 1), + data=Hash(0) + Hash(max_blobs_per_block - 1), to=BlobhashContext.address("staticcall"), ), { @@ -181,7 +207,7 @@ def opcode_context(yul: YulCompiler, request): ), }, tx_type_3.copy( - data=Hash(0) + Hash(SpecHelpers.max_blobs_per_block() - 1), + data=Hash(0) + Hash(max_blobs_per_block - 1), to=BlobhashContext.address("callcode"), ), { diff --git a/tests/cancun/eip4844_blobs/test_excess_blob_gas.py b/tests/cancun/eip4844_blobs/test_excess_blob_gas.py index 07d8bf9e95..56677de17a 100644 --- a/tests/cancun/eip4844_blobs/test_excess_blob_gas.py +++ b/tests/cancun/eip4844_blobs/test_excess_blob_gas.py @@ -22,10 +22,11 @@ """ # noqa: E501 import itertools -from typing import Dict, Iterator, List, Mapping, Optional, Tuple +from typing import Callable, Dict, Iterator, List, Mapping, Optional, Tuple import pytest +from ethereum_test_forks import Fork from ethereum_test_tools import ( EOA, Account, @@ -41,6 +42,7 @@ ) from ethereum_test_tools import Opcodes as Op from ethereum_test_tools import Transaction, add_kzg_version +from pytest_plugins import fork_covariant_parametrize from .spec import Spec, SpecHelpers, ref_spec_4844 @@ -52,27 +54,11 @@ @pytest.fixture -def parent_excess_blobs() -> int: # noqa: D103 +def parent_excess_blobs(fork: Fork) -> int: # noqa: D103 """ By default we start with an intermediate value between the target and max. """ - return (SpecHelpers.max_blobs_per_block() + SpecHelpers.target_blobs_per_block()) // 2 + 1 - - -@pytest.fixture -def parent_excess_blob_gas(parent_excess_blobs: int) -> int: # noqa: D103 - return parent_excess_blobs * Spec.GAS_PER_BLOB - - -@pytest.fixture -def correct_excess_blob_gas( # noqa: D103 - parent_excess_blob_gas: int, - parent_blobs: int, -) -> int: - return SpecHelpers.calc_excess_blob_gas_from_blob_count( - parent_excess_blob_gas=parent_excess_blob_gas, - parent_blob_count=parent_blobs, - ) + return (fork.max_blobs_per_block() + fork.target_blobs_per_block()) // 2 + 1 @pytest.fixture @@ -90,10 +76,11 @@ def header_excess_blob_gas( # noqa: D103 correct_excess_blob_gas: int, header_excess_blobs_delta: Optional[int], header_excess_blob_gas_delta: Optional[int], + blob_gas_per_blob: int, ) -> Optional[int]: if header_excess_blobs_delta is not None: modified_excess_blob_gas = correct_excess_blob_gas + ( - header_excess_blobs_delta * Spec.GAS_PER_BLOB + header_excess_blobs_delta * blob_gas_per_blob ) if modified_excess_blob_gas < 0: modified_excess_blob_gas = 2**64 + (modified_excess_blob_gas) @@ -104,58 +91,12 @@ def header_excess_blob_gas( # noqa: D103 @pytest.fixture -def block_fee_per_blob_gas( # noqa: D103 - correct_excess_blob_gas: int, -) -> int: - return Spec.get_blob_gasprice(excess_blob_gas=correct_excess_blob_gas) - - -@pytest.fixture -def block_base_fee() -> int: # noqa: D103 - return 7 - - -@pytest.fixture -def env( # noqa: D103 - parent_excess_blob_gas: int, - block_base_fee: int, - parent_blobs: int, -) -> Environment: - return Environment( - excess_blob_gas=( - parent_excess_blob_gas - if parent_blobs == 0 - else parent_excess_blob_gas + Spec.TARGET_BLOB_GAS_PER_BLOCK - ), - base_fee_per_gas=block_base_fee, - ) - - -@pytest.fixture -def tx_max_fee_per_gas( # noqa: D103 - block_base_fee: int, -) -> int: - return block_base_fee - - -@pytest.fixture -def tx_max_fee_per_blob_gas( # noqa: D103 - block_fee_per_blob_gas: int, -) -> int: - return block_fee_per_blob_gas - - -@pytest.fixture -def tx_data_cost( # noqa: D103 +def tx_blob_data_cost( # noqa: D103 tx_max_fee_per_blob_gas: int, new_blobs: int, + blob_gas_per_blob: int, ) -> int: - return tx_max_fee_per_blob_gas * Spec.GAS_PER_BLOB * new_blobs - - -@pytest.fixture -def tx_value() -> int: # noqa: D103 - return 1 + return tx_max_fee_per_blob_gas * blob_gas_per_blob * new_blobs @pytest.fixture @@ -165,9 +106,9 @@ def tx_gas_limit() -> int: # noqa: D103 @pytest.fixture def tx_exact_cost( # noqa: D103 - tx_value: int, tx_max_fee_per_gas: int, tx_data_cost: int, tx_gas_limit: int + tx_value: int, tx_max_fee_per_gas: int, tx_blob_data_cost: int, tx_gas_limit: int ) -> int: - return (tx_gas_limit * tx_max_fee_per_gas) + tx_value + tx_data_cost + return (tx_gas_limit * tx_max_fee_per_gas) + tx_value + tx_blob_data_cost @pytest.fixture @@ -191,11 +132,11 @@ def sender(pre: Alloc, tx_exact_cost: int) -> Address: # noqa: D103 @pytest.fixture def post( # noqa: D103 - destination_account: Address, tx_value: int, block_fee_per_blob_gas: int + destination_account: Address, tx_value: int, blob_gas_price: int ) -> Mapping[Address, Account]: return { destination_account: Account( - storage={0: block_fee_per_blob_gas}, + storage={0: blob_gas_price}, balance=tx_value, ), } @@ -248,8 +189,9 @@ def header_blob_gas_used() -> Optional[int]: # noqa: D103 @pytest.fixture def correct_blob_gas_used( # noqa: D103 tx: Transaction, + blob_gas_per_blob: int, ) -> int: - return Spec.get_total_blob_gas(tx) + return Spec.get_total_blob_gas(tx=tx, blob_gas_per_blob=blob_gas_per_blob) @pytest.fixture @@ -260,6 +202,8 @@ def blocks( # noqa: D103 correct_excess_blob_gas: int, correct_blob_gas_used: int, non_zero_blob_gas_used_genesis_block: Block, + max_blobs_per_block: int, + blob_gas_per_blob: int, ): blocks = ( [] @@ -292,7 +236,7 @@ def add_block( exception_message=BlockException.INCORRECT_EXCESS_BLOB_GAS, ) elif header_blob_gas_used is not None: - if header_blob_gas_used > Spec.MAX_BLOB_GAS_PER_BLOCK: + if header_blob_gas_used > (max_blobs_per_block * blob_gas_per_blob): add_block( header_modifier={"blob_gas_used": header_blob_gas_used}, exception_message=[ @@ -311,8 +255,14 @@ def add_block( return blocks -@pytest.mark.parametrize("parent_blobs", range(0, SpecHelpers.max_blobs_per_block() + 1)) -@pytest.mark.parametrize("parent_excess_blobs", range(0, SpecHelpers.target_blobs_per_block() + 1)) +@fork_covariant_parametrize( + parameter_names="parent_blobs", + fn=lambda fork: range(0, fork.max_blobs_per_block() + 1), +) +@fork_covariant_parametrize( + parameter_names="parent_excess_blobs", + fn=lambda fork: range(0, fork.target_blobs_per_block() + 1), +) @pytest.mark.parametrize("new_blobs", [1]) def test_correct_excess_blob_gas_calculation( blockchain_test: BlockchainTestFiller, @@ -338,26 +288,42 @@ def test_correct_excess_blob_gas_calculation( ) -BLOB_GAS_COST_INCREASES = [ - SpecHelpers.get_min_excess_blobs_for_blob_gas_price(i) - for i in [ - 2, # First blob gas cost increase - 2**32 // Spec.GAS_PER_BLOB, # Data tx wei cost 2^32 - 2**32, # blob gas cost 2^32 - 2**64 // Spec.GAS_PER_BLOB, # Data tx wei cost 2^64 - 2**64, # blob gas cost 2^64 - ( - 120_000_000 * (10**18) // Spec.GAS_PER_BLOB - ), # Data tx wei is current total Ether supply - ] -] +def generate_blob_gas_cost_increases_tests(delta: int) -> Callable[[Fork], List[int]]: + """ + Generates a list of block excess blob gas values where the blob gas price increases + based on fork properties. + """ + + def generator_function(fork: Fork) -> List[int]: + gas_per_blob = fork.blob_gas_per_blob() + return [ + SpecHelpers.get_min_excess_blobs_for_blob_gas_price( + fork=fork, blob_gas_price=blob_gas_price + ) + + delta + for blob_gas_price in [ + 2, # First blob gas cost increase + 2**32 // gas_per_blob, # Data tx wei cost 2^32 + 2**32, # blob gas cost 2^32 + 2**64 // gas_per_blob, # Data tx wei cost 2^64 + 2**64, # blob gas cost 2^64 + ( + 120_000_000 * (10**18) // gas_per_blob + ), # Data tx wei is current total Ether supply + ] + ] + return generator_function -@pytest.mark.parametrize( - "parent_excess_blobs", - [g - 1 for g in BLOB_GAS_COST_INCREASES], + +@fork_covariant_parametrize( + parameter_names="parent_excess_blobs", + fn=generate_blob_gas_cost_increases_tests(-1), +) +@fork_covariant_parametrize( + parameter_names="parent_blobs", + fn=lambda fork: [fork.target_blobs_per_block() + 1], ) -@pytest.mark.parametrize("parent_blobs", [SpecHelpers.target_blobs_per_block() + 1]) @pytest.mark.parametrize("new_blobs", [1]) def test_correct_increasing_blob_gas_costs( blockchain_test: BlockchainTestFiller, @@ -387,11 +353,14 @@ def test_correct_increasing_blob_gas_costs( ) -@pytest.mark.parametrize( - "parent_excess_blobs", - [g for g in BLOB_GAS_COST_INCREASES], +@fork_covariant_parametrize( + parameter_names="parent_excess_blobs", + fn=generate_blob_gas_cost_increases_tests(0), +) +@fork_covariant_parametrize( + parameter_names="parent_blobs", + fn=lambda fork: [fork.target_blobs_per_block() - 1], ) -@pytest.mark.parametrize("parent_blobs", [SpecHelpers.target_blobs_per_block() - 1]) @pytest.mark.parametrize("new_blobs", [1]) def test_correct_decreasing_blob_gas_costs( blockchain_test: BlockchainTestFiller, @@ -418,7 +387,10 @@ def test_correct_decreasing_blob_gas_costs( @pytest.mark.parametrize("header_excess_blob_gas", [0]) @pytest.mark.parametrize("new_blobs", [0, 1]) -@pytest.mark.parametrize("parent_blobs", range(0, SpecHelpers.max_blobs_per_block() + 1)) +@fork_covariant_parametrize( + parameter_names="parent_blobs", + fn=lambda fork: range(0, fork.max_blobs_per_block() + 1), +) def test_invalid_zero_excess_blob_gas_in_header( blockchain_test: BlockchainTestFiller, env: Environment, @@ -452,20 +424,21 @@ def test_invalid_zero_excess_blob_gas_in_header( ) -def all_invalid_blob_gas_used_combinations() -> Iterator[Tuple[int, int]]: +def all_invalid_blob_gas_used_combinations(fork: Fork) -> Iterator[Tuple[int, int]]: """ Returns all invalid blob gas used combinations. """ - for new_blobs in range(0, SpecHelpers.max_blobs_per_block() + 1): - for header_blob_gas_used in range(0, SpecHelpers.max_blobs_per_block() + 1): + gas_per_blob = fork.blob_gas_per_blob() + for new_blobs in range(0, fork.max_blobs_per_block() + 1): + for header_blob_gas_used in range(0, fork.max_blobs_per_block() + 1): if new_blobs != header_blob_gas_used: - yield (new_blobs, header_blob_gas_used * Spec.GAS_PER_BLOB) + yield (new_blobs, header_blob_gas_used * gas_per_blob) yield (new_blobs, 2**64 - 1) -@pytest.mark.parametrize( - "new_blobs,header_blob_gas_used", - all_invalid_blob_gas_used_combinations(), +@fork_covariant_parametrize( + parameter_names="new_blobs,header_blob_gas_used", + fn=all_invalid_blob_gas_used_combinations, ) @pytest.mark.parametrize("parent_blobs", [0]) def test_invalid_blob_gas_used_in_header( @@ -475,6 +448,7 @@ def test_invalid_blob_gas_used_in_header( blocks: List[Block], new_blobs: int, header_blob_gas_used: Optional[int], + blob_gas_per_blob: int, ): """ Test rejection of blocks where the `blobGasUsed` in the header is invalid: @@ -491,20 +465,26 @@ def test_invalid_blob_gas_used_in_header( genesis_environment=env, tag="-".join( [ - f"correct:{hex(new_blobs * Spec.GAS_PER_BLOB)}", + f"correct:{hex(new_blobs * blob_gas_per_blob)}", f"header:{hex(header_blob_gas_used)}", ] ), ) -@pytest.mark.parametrize( - "header_excess_blobs_delta,parent_blobs", - [ - (-1, 0), - (+1, SpecHelpers.max_blobs_per_block()), - ], - ids=["zero_blobs_decrease_more_than_expected", "max_blobs_increase_more_than_expected"], +def generate_invalid_excess_blob_gas_above_target_change_tests(fork: Fork) -> List: + """ + Returns all invalid excess blob gas above target change tests. + """ + return [ + pytest.param(-1, 0, id="zero_blobs_decrease_more_than_expected"), + pytest.param(+1, fork.max_blobs_per_block(), id="max_blobs_increase_more_than_expected"), + ] + + +@fork_covariant_parametrize( + parameter_names="header_excess_blobs_delta,parent_blobs", + fn=generate_invalid_excess_blob_gas_above_target_change_tests, ) @pytest.mark.parametrize("new_blobs", [1]) def test_invalid_excess_blob_gas_above_target_change( @@ -541,15 +521,15 @@ def test_invalid_excess_blob_gas_above_target_change( ) -@pytest.mark.parametrize( - "parent_blobs", - [ - b - for b in range(0, SpecHelpers.max_blobs_per_block() + 1) - if b != SpecHelpers.target_blobs_per_block() +@fork_covariant_parametrize( + parameter_names="parent_blobs", + fn=lambda fork: [ + b for b in range(0, fork.max_blobs_per_block() + 1) if b != fork.target_blobs_per_block() ], ) -@pytest.mark.parametrize("parent_excess_blobs", [1, SpecHelpers.target_blobs_per_block()]) +@fork_covariant_parametrize( + parameter_names="parent_excess_blobs", fn=lambda fork: [1, fork.target_blobs_per_block()] +) @pytest.mark.parametrize("new_blobs", [1]) def test_invalid_static_excess_blob_gas( blockchain_test: BlockchainTestFiller, @@ -582,8 +562,14 @@ def test_invalid_static_excess_blob_gas( ) -@pytest.mark.parametrize("header_excess_blobs_delta", range(1, SpecHelpers.max_blobs_per_block())) -@pytest.mark.parametrize("parent_blobs", range(0, SpecHelpers.target_blobs_per_block() + 1)) +@fork_covariant_parametrize( + parameter_names="header_excess_blobs_delta", + fn=lambda fork: range(1, fork.max_blobs_per_block()), +) +@fork_covariant_parametrize( + parameter_names="parent_blobs", + fn=lambda fork: range(0, fork.target_blobs_per_block() + 1), +) @pytest.mark.parametrize("parent_excess_blobs", [0]) # Start at 0 @pytest.mark.parametrize("new_blobs", [1]) def test_invalid_excess_blob_gas_target_blobs_increase_from_zero( @@ -621,9 +607,9 @@ def test_invalid_excess_blob_gas_target_blobs_increase_from_zero( @pytest.mark.parametrize("header_excess_blob_gas", [0]) -@pytest.mark.parametrize( - "parent_blobs", - range(SpecHelpers.target_blobs_per_block() + 1, SpecHelpers.max_blobs_per_block() + 1), +@fork_covariant_parametrize( + parameter_names="parent_blobs", + fn=lambda fork: range(fork.target_blobs_per_block() + 1, fork.max_blobs_per_block() + 1), ) @pytest.mark.parametrize("parent_excess_blobs", [0]) # Start at 0 @pytest.mark.parametrize("new_blobs", [1]) @@ -661,17 +647,15 @@ def test_invalid_static_excess_blob_gas_from_zero_on_blobs_above_target( ) -@pytest.mark.parametrize( - "parent_blobs,header_excess_blobs_delta", - itertools.product( +@fork_covariant_parametrize( + parameter_names="parent_blobs,header_excess_blobs_delta", + fn=lambda fork: itertools.product( # parent_blobs - range(0, SpecHelpers.max_blobs_per_block() + 1), + range(0, fork.max_blobs_per_block() + 1), # header_excess_blobs_delta (from correct value) [ x - for x in range( - -SpecHelpers.target_blobs_per_block(), SpecHelpers.target_blobs_per_block() + 1 - ) + for x in range(-fork.target_blobs_per_block(), fork.target_blobs_per_block() + 1) if x != 0 ], ), @@ -713,13 +697,22 @@ def test_invalid_excess_blob_gas_change( ) -@pytest.mark.parametrize( - "header_excess_blob_gas", - [(2**64 + (x * Spec.GAS_PER_BLOB)) for x in range(-SpecHelpers.target_blobs_per_block(), 0)], +@fork_covariant_parametrize( + parameter_names="header_excess_blob_gas", + fn=lambda fork: [ + (2**64 + (x * fork.blob_gas_per_blob())) + for x in range(-fork.target_blobs_per_block(), 0) + ], +) +@fork_covariant_parametrize( + parameter_names="parent_blobs", + fn=lambda fork: range(fork.target_blobs_per_block()), ) -@pytest.mark.parametrize("parent_blobs", range(SpecHelpers.target_blobs_per_block())) @pytest.mark.parametrize("new_blobs", [1]) -@pytest.mark.parametrize("parent_excess_blobs", range(SpecHelpers.target_blobs_per_block())) +@fork_covariant_parametrize( + parameter_names="parent_excess_blobs", + fn=lambda fork: range(fork.target_blobs_per_block()), +) def test_invalid_negative_excess_blob_gas( blockchain_test: BlockchainTestFiller, env: Environment, @@ -755,17 +748,20 @@ def test_invalid_negative_excess_blob_gas( ) -@pytest.mark.parametrize( - "parent_blobs,header_excess_blob_gas_delta", - [ - (SpecHelpers.target_blobs_per_block() + 1, 1), - (SpecHelpers.target_blobs_per_block() + 1, Spec.GAS_PER_BLOB - 1), - (SpecHelpers.target_blobs_per_block() - 1, -1), - (SpecHelpers.target_blobs_per_block() - 1, -(Spec.GAS_PER_BLOB - 1)), +@fork_covariant_parametrize( + parameter_names="parent_blobs,header_excess_blob_gas_delta", + fn=lambda fork: [ + (fork.target_blobs_per_block() + 1, 1), + (fork.target_blobs_per_block() + 1, fork.blob_gas_per_blob() - 1), + (fork.target_blobs_per_block() - 1, -1), + (fork.target_blobs_per_block() - 1, -(fork.blob_gas_per_blob() - 1)), ], ) @pytest.mark.parametrize("new_blobs", [1]) -@pytest.mark.parametrize("parent_excess_blobs", [SpecHelpers.target_blobs_per_block() + 1]) +@fork_covariant_parametrize( + parameter_names="parent_excess_blobs", + fn=lambda fork: [fork.target_blobs_per_block() + 1], +) def test_invalid_non_multiple_excess_blob_gas( blockchain_test: BlockchainTestFiller, env: Environment, diff --git a/tests/cancun/eip4844_blobs/test_excess_blob_gas_fork_transition.py b/tests/cancun/eip4844_blobs/test_excess_blob_gas_fork_transition.py index 08eaa87239..e3d9b2c916 100644 --- a/tests/cancun/eip4844_blobs/test_excess_blob_gas_fork_transition.py +++ b/tests/cancun/eip4844_blobs/test_excess_blob_gas_fork_transition.py @@ -7,6 +7,7 @@ import pytest +from ethereum_test_forks import Cancun, Fork from ethereum_test_tools import ( Account, Address, @@ -56,12 +57,12 @@ def pre_fork_blocks(): @pytest.fixture -def post_fork_block_count() -> int: +def post_fork_block_count(fork: Fork) -> int: """ Amount of blocks to produce with the post-fork rules. """ - return SpecHelpers.get_min_excess_blobs_for_blob_gas_price(2) // ( - SpecHelpers.max_blobs_per_block() - SpecHelpers.target_blobs_per_block() + return SpecHelpers.get_min_excess_blobs_for_blob_gas_price(fork=fork, blob_gas_price=2) // ( + fork.max_blobs_per_block() - fork.target_blobs_per_block() ) @@ -225,13 +226,13 @@ def test_invalid_post_fork_block_without_blob_fields( "post_fork_block_count,blob_count_per_block", [ ( - SpecHelpers.get_min_excess_blobs_for_blob_gas_price(2) - // (SpecHelpers.max_blobs_per_block() - SpecHelpers.target_blobs_per_block()) + SpecHelpers.get_min_excess_blobs_for_blob_gas_price(fork=Cancun, blob_gas_price=2) + // (Cancun.max_blobs_per_block() - Cancun.target_blobs_per_block()) + 2, - SpecHelpers.max_blobs_per_block(), + Cancun.max_blobs_per_block(), ), (10, 0), - (10, SpecHelpers.target_blobs_per_block()), + (10, Cancun.target_blobs_per_block()), ], ids=["max_blobs", "no_blobs", "target_blobs"], ) diff --git a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py index ee819b3d71..c25839e994 100644 --- a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py +++ b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py @@ -2768,6 +2768,7 @@ def test_eoa_tx_after_set_code( blockchain_test: BlockchainTestFiller, pre: Alloc, tx_type: int, + fork: Fork, evm_code_type: EVMCodeType, ): """ @@ -2855,7 +2856,7 @@ def test_eoa_tx_after_set_code( value=0, max_fee_per_gas=1_000, max_priority_fee_per_gas=1_000, - max_fee_per_blob_gas=1_000, + max_fee_per_blob_gas=fork.min_base_fee_per_blob_gas() * 10, blob_versioned_hashes=add_kzg_version( [Hash(1)], Spec4844.BLOB_COMMITMENT_VERSION_KZG,