diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 727ab2fe7ff..b033230ddd9 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -74,6 +74,7 @@ Users can select any of the artifacts depending on their testing needs for their - ✨ Introduce blockchain tests for ZKEVM to cover the scenario of pure ether transfers [#1742](https://github.com/ethereum/execution-spec-tests/pull/1742). - ✨ [EIP-7934](https://eips.ethereum.org/EIPS/eip-7934): Add test cases for the block RLP max limit of 10MiB ([#1730](https://github.com/ethereum/execution-spec-tests/pull/1730)). - ✨ [EIP-7939](https://eips.ethereum.org/EIPS/eip-7939) Add count leading zeros (CLZ) opcode tests for Osaka ([#1733](https://github.com/ethereum/execution-spec-tests/pull/1733)). +- ✨ [EIP-7918](https://eips.ethereum.org/EIPS/eip-7918): Blob base fee bounded by execution cost test cases (initial), includes some adjustments to [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844) tests ([#1685](https://github.com/ethereum/execution-spec-tests/pull/1685)). ## [v4.5.0](https://github.com/ethereum/execution-spec-tests/releases/tag/v4.5.0) - 2025-05-14 diff --git a/src/ethereum_test_forks/base_fork.py b/src/ethereum_test_forks/base_fork.py index 69501ce2d1f..006e8b671ec 100644 --- a/src/ethereum_test_forks/base_fork.py +++ b/src/ethereum_test_forks/base_fork.py @@ -96,6 +96,7 @@ def __call__( parent_excess_blobs: int | None = None, parent_blob_gas_used: int | None = None, parent_blob_count: int | None = None, + parent_base_fee_per_gas: int, ) -> int: """Return the excess blob gas given the parent's excess blob gas and blob gas used.""" pass diff --git a/src/ethereum_test_forks/forks/forks.py b/src/ethereum_test_forks/forks/forks.py index 6e9e7423277..8b258802ab4 100644 --- a/src/ethereum_test_forks/forks/forks.py +++ b/src/ethereum_test_forks/forks/forks.py @@ -960,6 +960,7 @@ def fn( parent_excess_blobs: int | None = None, parent_blob_gas_used: int | None = None, parent_blob_count: int | None = None, + parent_base_fee_per_gas: int, # Required for Osaka as using this as base ) -> int: if parent_excess_blob_gas is None: assert parent_excess_blobs is not None, "Parent excess blobs are required" @@ -1388,6 +1389,55 @@ def precompiles(cls, block_number: int = 0, timestamp: int = 0) -> List[Address] """ return [Address(0x100)] + super(Osaka, cls).precompiles(block_number, timestamp) + @classmethod + def excess_blob_gas_calculator( + cls, block_number: int = 0, timestamp: int = 0 + ) -> ExcessBlobGasCalculator: + """Return a callable that calculates the excess blob gas for a block.""" + target_blobs_per_block = cls.target_blobs_per_block(block_number, timestamp) + blob_gas_per_blob = cls.blob_gas_per_blob(block_number, timestamp) + target_blob_gas_per_block = target_blobs_per_block * blob_gas_per_blob + max_blobs_per_block = cls.max_blobs_per_block(block_number, timestamp) + blob_base_cost = 2**14 # EIP-7918 new parameter + + def fn( + *, + parent_excess_blob_gas: int | None = None, + parent_excess_blobs: int | None = None, + parent_blob_gas_used: int | None = None, + parent_blob_count: int | None = None, + parent_base_fee_per_gas: int, # EIP-7918 additional parameter + ) -> int: + if parent_excess_blob_gas is None: + assert parent_excess_blobs is not None, "Parent excess blobs are required" + parent_excess_blob_gas = parent_excess_blobs * blob_gas_per_blob + if parent_blob_gas_used is None: + assert parent_blob_count is not None, "Parent blob count is required" + parent_blob_gas_used = parent_blob_count * blob_gas_per_blob + if parent_excess_blob_gas + parent_blob_gas_used < target_blob_gas_per_block: + return 0 + + # EIP-7918: Apply reserve price when execution costs dominate blob costs + current_blob_base_fee = cls.blob_gas_price_calculator()( + excess_blob_gas=parent_excess_blob_gas + ) + reserve_price_active = ( + blob_base_cost * parent_base_fee_per_gas + > blob_gas_per_blob * current_blob_base_fee + ) + if reserve_price_active: + blob_excess_adjustment = ( + parent_blob_gas_used + * (max_blobs_per_block - target_blobs_per_block) + // max_blobs_per_block + ) + return parent_excess_blob_gas + blob_excess_adjustment + + # Original EIP-4844 calculation + return parent_excess_blob_gas + parent_blob_gas_used - target_blob_gas_per_block + + return fn + class EOFv1(Prague, solc_name="cancun"): """EOF fork.""" diff --git a/tests/cancun/eip4844_blobs/conftest.py b/tests/cancun/eip4844_blobs/conftest.py index a0e92e5a3ca..7a27459fa52 100644 --- a/tests/cancun/eip4844_blobs/conftest.py +++ b/tests/cancun/eip4844_blobs/conftest.py @@ -70,6 +70,7 @@ def excess_blob_gas( fork: Fork, parent_excess_blobs: int | None, parent_blobs: int | None, + block_base_fee_per_gas: int, ) -> int | None: """ Calculate the excess blob gas of the block under test from the parent block. @@ -78,10 +79,10 @@ def 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( + return fork.excess_blob_gas_calculator()( parent_excess_blobs=parent_excess_blobs, parent_blob_count=parent_blobs, + parent_base_fee_per_gas=block_base_fee_per_gas, ) @@ -90,6 +91,7 @@ def correct_excess_blob_gas( fork: Fork, parent_excess_blobs: int | None, parent_blobs: int | None, + block_base_fee_per_gas: int, ) -> int: """ Calculate the correct excess blob gas of the block under test from the parent block. @@ -98,18 +100,19 @@ def correct_excess_blob_gas( """ 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( + return fork.excess_blob_gas_calculator()( parent_excess_blobs=parent_excess_blobs, parent_blob_count=parent_blobs, + parent_base_fee_per_gas=block_base_fee_per_gas, ) @pytest.fixture -def block_fee_per_blob_gas( # noqa: D103 +def block_fee_per_blob_gas( fork: Fork, correct_excess_blob_gas: int, ) -> int: + """Calculate the blob gas price for the current block.""" get_blob_gas_price = fork.blob_gas_price_calculator() return get_blob_gas_price(excess_blob_gas=correct_excess_blob_gas) @@ -251,7 +254,7 @@ def non_zero_blob_gas_used_genesis_block( genesis_excess_blob_gas: int, parent_excess_blob_gas: int, tx_max_fee_per_gas: int, - target_blobs_per_block: int, + block_base_fee_per_gas: int, ) -> Block | None: """ For test cases with a non-zero blobGasUsed field in the @@ -271,10 +274,17 @@ def non_zero_blob_gas_used_genesis_block( return None excess_blob_gas_calculator = fork.excess_blob_gas_calculator(block_number=1) - assert parent_excess_blob_gas == excess_blob_gas_calculator( + calculated_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" + parent_base_fee_per_gas=block_base_fee_per_gas, + ) + + assert parent_excess_blob_gas == calculated_excess_blob_gas, ( + f"parent excess blob gas mismatch: expected {parent_excess_blob_gas}, " + f"got {calculated_excess_blob_gas} for {parent_blobs} blobs " + f"with base_fee_per_gas {block_base_fee_per_gas}" + ) sender = pre.fund_eoa(10**27) diff --git a/tests/cancun/eip4844_blobs/spec.py b/tests/cancun/eip4844_blobs/spec.py index fc1be76b443..4b5efcf2495 100644 --- a/tests/cancun/eip4844_blobs/spec.py +++ b/tests/cancun/eip4844_blobs/spec.py @@ -45,6 +45,7 @@ class Spec: # LIMIT_BLOBS_PER_TX = 2**12 HASH_OPCODE_BYTE = 0x49 HASH_GAS_COST = 3 + GAS_PER_BLOB = 2**17 @classmethod def kzg_to_versioned_hash( diff --git a/tests/cancun/eip4844_blobs/test_blob_txs.py b/tests/cancun/eip4844_blobs/test_blob_txs.py index ae776ee0ad0..6ff1ff7dacb 100644 --- a/tests/cancun/eip4844_blobs/test_blob_txs.py +++ b/tests/cancun/eip4844_blobs/test_blob_txs.py @@ -312,6 +312,7 @@ def expected_excess_blob_gas( parent_blobs: Optional[int], block_number: int, block_timestamp: int, + block_base_fee_per_gas: int, ) -> Optional[int | Removable]: """Calculate blob gas used by the test block.""" if not fork.header_excess_blob_gas_required( @@ -322,6 +323,7 @@ def expected_excess_blob_gas( 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, + parent_base_fee_per_gas=block_base_fee_per_gas, ) @@ -374,6 +376,7 @@ def block( "blobs_per_tx", SpecHelpers.all_valid_blob_combinations, ) +@pytest.mark.parametrize("block_base_fee_per_gas", [7, 100]) @pytest.mark.valid_from("Cancun") def test_valid_blob_tx_combinations( blockchain_test: BlockchainTestFiller, @@ -664,6 +667,7 @@ def test_insufficient_balance_blob_tx( [b"", b"\x00", b"\x01"], ids=["no_calldata", "single_zero_calldata", "single_one_calldata"], ) +@pytest.mark.parametrize("block_base_fee_per_gas", [7, 100]) @pytest.mark.parametrize("tx_max_fee_per_blob_gas_multiplier", [1, 100, 10000]) @pytest.mark.valid_from("Cancun") def test_sufficient_balance_blob_tx( 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 ee2b48ede47..1f13736814d 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 @@ -131,6 +131,7 @@ def fork_block_excess_blob_gas( fork: Fork, pre_fork_excess_blobs: int, pre_fork_blobs_per_block: int, + block_base_fee_per_gas: int, ) -> int: """Calculate the expected excess blob gas for the fork block.""" if pre_fork_blobs_per_block == 0: @@ -139,6 +140,7 @@ def fork_block_excess_blob_gas( return calc_excess_blob_gas_post_fork( parent_excess_blobs=pre_fork_excess_blobs, parent_blob_count=pre_fork_blobs_per_block, + parent_base_fee_per_gas=block_base_fee_per_gas, ) @@ -382,6 +384,7 @@ def test_fork_transition_excess_blob_gas_at_blob_genesis( ), ], ) +@pytest.mark.parametrize("block_base_fee_per_gas", [7, 16, 23]) def test_fork_transition_excess_blob_gas_post_blob_genesis( blockchain_test: BlockchainTestFiller, env: Environment, diff --git a/tests/osaka/eip7594_peerdas/test_get_blobs.py b/tests/osaka/eip7594_peerdas/test_get_blobs.py index bb5800c5d1c..13c836dda18 100644 --- a/tests/osaka/eip7594_peerdas/test_get_blobs.py +++ b/tests/osaka/eip7594_peerdas/test_get_blobs.py @@ -51,6 +51,12 @@ def tx_gas() -> int: return 21_000 +@pytest.fixture +def block_base_fee_per_gas() -> int: + """Return default max fee per gas for transactions sent during test.""" + return 7 + + @pytest.fixture def tx_calldata() -> bytes: """Calldata in transactions sent during test.""" @@ -83,6 +89,7 @@ def excess_blob_gas( fork: Fork, parent_excess_blobs: int | None, parent_blobs: int | None, + block_base_fee_per_gas: int, ) -> int | None: """ Calculate the excess blob gas of the block under test from the parent block. @@ -95,6 +102,7 @@ def excess_blob_gas( return excess_blob_gas( parent_excess_blobs=parent_excess_blobs, parent_blob_count=parent_blobs, + parent_base_fee_per_gas=block_base_fee_per_gas, ) diff --git a/tests/osaka/eip7918_blob_reserve_price/__init__.py b/tests/osaka/eip7918_blob_reserve_price/__init__.py new file mode 100644 index 00000000000..47dc15cac5b --- /dev/null +++ b/tests/osaka/eip7918_blob_reserve_price/__init__.py @@ -0,0 +1 @@ +"""Cross-client EIP-7918 Tests.""" diff --git a/tests/osaka/eip7918_blob_reserve_price/conftest.py b/tests/osaka/eip7918_blob_reserve_price/conftest.py new file mode 100644 index 00000000000..027cf971349 --- /dev/null +++ b/tests/osaka/eip7918_blob_reserve_price/conftest.py @@ -0,0 +1,209 @@ +""" +Pytest (plugin) definitions local to EIP-7918 tests. + +Mostly a copy of `tests/cancun/eip4844_blobs/conftest.py`. +""" + +import pytest + +from ethereum_test_forks import Fork +from ethereum_test_tools import Environment + +from .spec import Spec + + +@pytest.fixture +def target_blobs_per_block(fork: Fork) -> int: + """Return default number of target blobs per block.""" + return fork.target_blobs_per_block() + + +@pytest.fixture +def max_blobs_per_block(fork: Fork) -> int: + """Return default number of max blobs per block.""" + return fork.max_blobs_per_block() + + +@pytest.fixture +def blob_gas_per_blob(fork: Fork) -> int: + """Return default blob gas cost per blob.""" + return fork.blob_gas_per_blob() + + +@pytest.fixture(autouse=True) +def parent_excess_blobs() -> int | None: + """ + Return 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: + """ + Return default data blobs of the parent block. + + 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: + """Calculate 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 blobs_per_tx() -> int: + """ + Total number of blobs per transaction. + + Can be overloaded by a test case to provide a custom blobs per transaction count. + """ + return 1 + + +@pytest.fixture +def block_base_fee_per_gas_delta() -> int: + """Delta to add to the block base fee. Default is 0.""" + return 0 + + +@pytest.fixture +def block_base_fee_per_gas( + fork: Fork, + parent_excess_blobs: int | None, + block_base_fee_per_gas_delta: int, +) -> int: + """Block base fee per gas. Default is 7 unless a delta is provided or overloaded.""" + if block_base_fee_per_gas_delta != 0: + if parent_excess_blobs is None: + blob_base_fee = 1 + else: + excess_blob_gas = parent_excess_blobs * fork.blob_gas_per_blob() + blob_gas_price_calculator = fork.blob_gas_price_calculator() + blob_base_fee = blob_gas_price_calculator(excess_blob_gas=excess_blob_gas) + boundary_base_fee = 8 * blob_base_fee + return boundary_base_fee + block_base_fee_per_gas_delta + return 7 + + +@pytest.fixture +def excess_blob_gas( + fork: Fork, + parent_excess_blobs: int | None, + parent_blobs: int | None, + block_base_fee_per_gas: int, +) -> int | None: + """ + Calculate 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 + return fork.excess_blob_gas_calculator()( + parent_excess_blobs=parent_excess_blobs, + parent_blob_count=parent_blobs, + parent_base_fee_per_gas=block_base_fee_per_gas, + ) + + +@pytest.fixture +def correct_excess_blob_gas( + fork: Fork, + parent_excess_blobs: int | None, + parent_blobs: int | None, + block_base_fee_per_gas: int, +) -> int: + """ + Calculate 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 + return fork.excess_blob_gas_calculator()( + parent_excess_blobs=parent_excess_blobs, + parent_blob_count=parent_blobs, + parent_base_fee_per_gas=block_base_fee_per_gas, + ) + + +@pytest.fixture +def blob_gas_price( + fork: Fork, + excess_blob_gas: int | None, +) -> int | None: + """Return 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 correct_blob_gas_used( + fork: Fork, + blobs_per_tx: int, +) -> int: + """Correct blob gas used by the test transaction.""" + return fork.blob_gas_per_blob() * blobs_per_tx + + +@pytest.fixture +def reserve_price( + block_base_fee_per_gas: int, +) -> int: + """Calculate the blob base fee reserve price for the current base fee.""" + return Spec.get_reserve_price(block_base_fee_per_gas) + + +@pytest.fixture +def is_reserve_price_active( + block_base_fee_per_gas: int, + blob_gas_price: int, +) -> bool: + """Check if the reserve price mechanism should be active.""" + return Spec.is_reserve_price_active(block_base_fee_per_gas, blob_gas_price) + + +@pytest.fixture +def genesis_excess_blob_gas( + parent_excess_blob_gas: int | None, +) -> int: + """Return default excess blob gas for the genesis block.""" + return parent_excess_blob_gas if parent_excess_blob_gas else 0 + + +@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_max_fee_per_blob_gas(blob_gas_price: int | None) -> int: + """Max fee per blob gas based on actual blob gas price.""" + if blob_gas_price is None: + return 1 + return blob_gas_price diff --git a/tests/osaka/eip7918_blob_reserve_price/spec.py b/tests/osaka/eip7918_blob_reserve_price/spec.py new file mode 100644 index 00000000000..a64e8a420dd --- /dev/null +++ b/tests/osaka/eip7918_blob_reserve_price/spec.py @@ -0,0 +1,55 @@ +"""Defines EIP-7918 specification constants and functions.""" + +from dataclasses import dataclass + +# Base the spec on EIP-4844 which EIP-7918 extends +from ...cancun.eip4844_blobs.spec import Spec as EIP4844Spec + + +@dataclass(frozen=True) +class ReferenceSpec: + """Defines the reference spec version and git path.""" + + git_path: str + version: str + + +ref_spec_7918 = ReferenceSpec("EIPS/eip-7918.md", "be1dbefafcb40879e3f6d231fad206c62f5b371b") + + +@dataclass(frozen=True) +class Spec(EIP4844Spec): + """ + Parameters from the EIP-7918 specifications. + Extends EIP-4844 spec with the new reserve price constant and functionality. + """ + + BLOB_BASE_COST = 2**14 + + @classmethod + def get_reserve_price( + cls, + base_fee_per_gas: int, + ) -> int: + """Calculate the reserve price for blob gas given the blob base fee.""" + return (cls.BLOB_BASE_COST * base_fee_per_gas) // cls.GAS_PER_BLOB + + @classmethod + def is_reserve_price_active( + cls, + base_fee_per_gas: int, + blob_base_fee: int, + ) -> bool: + """Check if the reserve price mechanism should be active.""" + reserve_price = cls.get_reserve_price(base_fee_per_gas) + return reserve_price > blob_base_fee + + @classmethod + def calc_effective_blob_base_fee( + cls, + base_fee_per_gas: int, + blob_base_fee: int, + ) -> int: + """Calculate the effective blob base fee considering the reserve price.""" + reserve_price = cls.get_reserve_price(base_fee_per_gas) + return max(reserve_price, blob_base_fee) diff --git a/tests/osaka/eip7918_blob_reserve_price/test_blob_base_fee.py b/tests/osaka/eip7918_blob_reserve_price/test_blob_base_fee.py new file mode 100644 index 00000000000..1b1d902b316 --- /dev/null +++ b/tests/osaka/eip7918_blob_reserve_price/test_blob_base_fee.py @@ -0,0 +1,187 @@ +""" +abstract: [EIP-7918: Blob base fee bounded by execution cost](https://eips.ethereum.org/EIPS/eip-7918) + Test the blob base fee reserve price mechanism for [EIP-7918: Blob base fee bounded by execution cost](https://eips.ethereum.org/EIPS/eip-7918). + +""" # noqa: E501 + +from typing import Dict, List + +import pytest + +from ethereum_test_forks import Fork +from ethereum_test_tools import ( + Account, + Address, + Alloc, + Block, + BlockchainTestFiller, + Environment, + Hash, + Header, + Transaction, + add_kzg_version, +) +from ethereum_test_tools import Opcodes as Op + +from .spec import Spec, ref_spec_7918 + +REFERENCE_SPEC_GIT_PATH = ref_spec_7918.git_path +REFERENCE_SPEC_VERSION = ref_spec_7918.version + +pytestmark = pytest.mark.valid_from("Osaka") + + +@pytest.fixture +def sender(pre: Alloc) -> Address: + """Sender account with enough balance for tests.""" + return pre.fund_eoa(10**18) + + +@pytest.fixture +def destination_account(pre: Alloc) -> Address: + """Contract that stores the blob base fee for verification.""" + code = Op.SSTORE(0, Op.BLOBBASEFEE) + return pre.deploy_contract(code) + + +@pytest.fixture +def tx_gas() -> int: + """Gas limit for transactions sent during test.""" + return 100_000 + + +@pytest.fixture +def tx_value() -> int: + """Value for transactions sent during test.""" + return 1 + + +@pytest.fixture +def blob_hashes_per_tx(blobs_per_tx: int) -> List[Hash]: + """Blob hashes for the transaction.""" + return add_kzg_version( + [Hash(x) for x in range(blobs_per_tx)], + Spec.BLOB_COMMITMENT_VERSION_KZG, + ) + + +@pytest.fixture +def tx( + sender: Address, + destination_account: Address, + tx_gas: int, + tx_value: int, + blob_hashes_per_tx: List[Hash], + block_base_fee_per_gas: int, + tx_max_fee_per_blob_gas: int, +) -> Transaction: + """Blob transaction for the block.""" + return Transaction( + ty=Spec.BLOB_TX_TYPE, + sender=sender, + to=destination_account, + value=tx_value, + gas_limit=tx_gas, + max_fee_per_gas=block_base_fee_per_gas, + max_priority_fee_per_gas=0, + max_fee_per_blob_gas=tx_max_fee_per_blob_gas, + access_list=[], + blob_versioned_hashes=blob_hashes_per_tx, + ) + + +@pytest.fixture +def block( + tx: Transaction, + fork: Fork, + parent_excess_blobs: int, + block_base_fee_per_gas: int, + blob_gas_per_blob: int, +) -> Block: + """Single block fixture.""" + blob_count = len(tx.blob_versioned_hashes) if tx.blob_versioned_hashes else 0 + excess_blob_gas_calculator = fork.excess_blob_gas_calculator() + expected_excess_blob_gas = excess_blob_gas_calculator( + parent_excess_blobs=parent_excess_blobs, + parent_blob_count=0, + parent_base_fee_per_gas=block_base_fee_per_gas, + ) + return Block( + txs=[tx], + header_verify=Header( + excess_blob_gas=expected_excess_blob_gas, + blob_gas_used=blob_count * blob_gas_per_blob, + ), + ) + + +@pytest.fixture +def post( + destination_account: Address, + blob_gas_price: int, + tx_value: int, +) -> Dict[Address, Account]: + """Post state storing the effective blob base fee.""" + return { + destination_account: Account( + storage={0: blob_gas_price}, + balance=tx_value, + ) + } + + +@pytest.mark.parametrize( + "block_base_fee_per_gas", + [1, 7, 15, 16, 17, 100, 1000, 10000], +) +@pytest.mark.parametrize_by_fork( + "parent_excess_blobs", + lambda fork: range(0, fork.target_blobs_per_block() + 1), +) +def test_reserve_price_various_base_fee_scenarios( + blockchain_test: BlockchainTestFiller, + env: Environment, + pre: Alloc, + block: Block, + post: Dict[Address, Account], +): + """Test reserve price mechanism across various block base fee and excess blob gas scenarios.""" + blockchain_test( + pre=pre, + post=post, + blocks=[block], + genesis_environment=env, + ) + + +@pytest.mark.parametrize_by_fork( + "parent_excess_blobs", + # Keep max assuming this will be greater than 20 in the future, to test a blob fee of > 1 :) + lambda fork: [0, 3, fork.target_blobs_per_block(), fork.max_blobs_per_block()], +) +@pytest.mark.parametrize("block_base_fee_per_gas_delta", [-2, -1, 0, 1, 10, 100]) +def test_reserve_price_boundary( + blockchain_test: BlockchainTestFiller, + env: Environment, + pre: Alloc, + block: Block, + post: Dict[Address, Account], +): + """ + Tests the reserve price boundary mechanism. Note the default block base fee per gas is 7 (delta is 0). + With a non zero delta the block base fee per gas is set to (boundary * blob base fee) + delta. + + Example scenarios from parametrization, assume parent_excess_blobs = 3: + delta=-2: blob_base_fee=1, boundary=8, block_base_fee_per_gas=8+(-2)=6, 6 < 8, reserve inactive, effective_fee=1 + delta=0: blob_base_fee=1, boundary=8, block_base_fee_per_gas=7, 7 < 8, reserve inactive, effective_fee=1 + delta=100: blob_base_fee=1, boundary=8, block_base_fee_per_gas=8+100=108, 108 > 8, reserve active, effective_fee=max(108/8, 1)=13 + + All values give a blob base_ fee of 1 because we need a much higher excess blob gas + to increase the blob fee. This only increases to 2 at 20 excess blobs. + """ # noqa: E501 + blockchain_test( + genesis_environment=env, + pre=pre, + blocks=[block], + post=post, + )