Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@
def sstore_tx_gas(fork: Fork, num_sstores: int = 1) -> tuple[int, int]:
"""Return (regular, state) gas for a tx with N cold SSTOREs."""
intrinsic_gas = fork.transaction_intrinsic_cost_calculator()()
evm_total = num_sstores * Op.SSTORE(0, 1).gas_cost(fork)
evm_total = num_sstores * Op.SSTORE(0, 1).regular_cost(fork)
state = num_sstores * Op.SSTORE(new_value=1).state_cost(fork)
return intrinsic_gas + evm_total - state, state
return intrinsic_gas + evm_total, state


def sstore_txs(
Expand Down Expand Up @@ -793,18 +793,26 @@ def test_receipt_cumulative_differs_from_header_gas_used(


@pytest.mark.parametrize("dominant_dimension", ["state", "regular"])
@pytest.mark.parametrize(
"single_tx",
[
pytest.param(True, id="single_tx"),
pytest.param(False, id="multiple_txs"),
],
)
@pytest.mark.valid_from("EIP8037")
def test_base_fee_per_gas_follows_dominant_dimension(
blockchain_test: BlockchainTestFiller,
pre: Alloc,
fork: Fork,
dominant_dimension: str,
single_tx: bool,
) -> None:
"""
Verify the child block's base fee follows the bottleneck dimension.

Block 1 exceeds the gas target on one dimension only: state, via
SSTORE-set txs that spill, or regular, via STOP txs. Its header
SSTORE-set txs that spill, or regular, via STOP/MSTORE txs. Its header
gas_used = max(regular, state) is then set by that dimension alone,
which lifts empty block 2's base fee under the EIP-1559 update.
"""
Expand All @@ -815,30 +823,53 @@ def test_base_fee_per_gas_follows_dominant_dimension(

txs: list[Transaction] = []
post: dict = {}
num_sstores = 0
if dominant_dimension == "state":
num_txs = 5
tx_regular, tx_state = sstore_tx_gas(fork)
if single_tx:
num_txs = 1
num_sstores = target // sstore_tx_gas(fork, num_sstores=1)[1] + 1
tx_regular, tx_state = sstore_tx_gas(fork, num_sstores=num_sstores)
else:
num_sstores = 1
tx_regular, tx_state = sstore_tx_gas(fork, num_sstores=num_sstores)
while tx_regular >= tx_state:
num_sstores += 1
tx_regular, tx_state = sstore_tx_gas(
fork, num_sstores=num_sstores
)
num_txs = target // tx_state + 1
block_regular = num_txs * tx_regular
block_state = num_txs * tx_state
tx_gas_limit = tx_regular + tx_state
assert block_state > target > block_regular
else:
num_txs = 15
tx_gas_limit = fork.transaction_intrinsic_cost_calculator()()
if single_tx:
num_txs = 1
# Just consume all gas
regular_contract = pre.deploy_contract(
code=Op.MSTORE(offset=2**256 - 1, value=1) + Op.STOP
)
tx_gas_limit = target + 1
else:
tx_gas_limit = fork.transaction_intrinsic_cost_calculator()()
# Enough STOP txs that regular gas alone clears the target.
regular_contract = pre.deploy_contract(code=Op.STOP)
num_txs = target // tx_gas_limit + 1
block_regular = num_txs * tx_gas_limit
block_state = 0
stop_contract = pre.deploy_contract(code=Op.STOP)
assert block_regular > target > block_state

for _ in range(num_txs):
if dominant_dimension == "state":
storage = Storage()
contract = pre.deploy_contract(
code=Op.SSTORE(storage.store_next(1), 1) + Op.STOP,
)
code = Bytecode()
for _ in range(num_sstores):
code += Op.SSTORE(storage.store_next(1), 1)
code += Op.STOP
contract = pre.deploy_contract(code=code)
post[contract] = Account(storage=storage)
else:
contract = stop_contract
contract = regular_contract
txs.append(
Transaction(
to=contract,
Expand All @@ -850,6 +881,10 @@ def test_base_fee_per_gas_follows_dominant_dimension(
)

block_1_gas_used = max(block_regular, block_state)
assert block_1_gas_used < gas_limit, (
"test needs update: gas_limit reached by usage, simply raise the "
"anchored gas_limit value"
)
base_fee_calc = fork.base_fee_per_gas_calculator()
block_1_base_fee = base_fee_calc(
parent_base_fee_per_gas=genesis_base_fee,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,9 @@ def test_selfdestruct_state_gas_refilled_on_ancestor_revert(

The inner frame spills the NEW_ACCOUNT charge and self-destructs
successfully, then the caller reverts: the beneficiary creation
rolls back and the spilled charge is refilled, so only regular gas
is billed.
rolls back and the spilled state charge is refilled. The EIP-8038
regular account-write charge for the attempted empty-account value
transfer remains billed.
"""
beneficiary = 0xDEAD
inner_code = Op.SELFDESTRUCT(beneficiary)
Expand All @@ -240,6 +241,7 @@ def test_selfdestruct_state_gas_refilled_on_ancestor_revert(
fork.transaction_intrinsic_cost_calculator()()
+ caller_code.gas_cost(fork)
+ inner_code.gas_cost(fork)
+ fork.gas_costs().ACCOUNT_WRITE
)
tx = Transaction(to=caller, sender=pre.fund_eoa())

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,27 +327,28 @@ def test_mixed_validity_multi_auth_receipt_gas(
one invalid authorization.

Every authorization tuple, valid or invalid, is charged the full
regular + state per-authorization intrinsic. Refunds, however, fire
only for the *valid* authorization whose authority leaf already
exists: it refills ``NEW_ACCOUNT`` on the state channel (uncapped,
subtracted first) and returns ``ACCOUNT_WRITE`` on the regular
channel (one-fifth capped). The invalid tuple is silently skipped
during ``set_delegation`` and contributes no refund on either
channel.
regular + state per-authorization intrinsic. The valid
authorization whose authority leaf already exists refills
``NEW_ACCOUNT`` on the state channel (uncapped, subtracted first)
and returns ``ACCOUNT_WRITE`` on the regular channel (one-fifth
capped). The invalid tuple is silently skipped during
``set_delegation``, refilling the full per-auth state intrinsic and
returning its regular ``ACCOUNT_WRITE`` charge.

The dual-channel accounting mirrors ``process_transaction`` and the
sibling ``test_set_code_auth_refunds`` module: the state refill is
subtracted from ``gas_before_regular_refund`` first and uncapped,
then the regular refund clamps to
``min(k * ACCOUNT_WRITE, gas_before_regular_refund // 5)`` where
``k`` is the number of refundable (valid, existing-leaf)
authorizations. With no EVM execution, ``gas_before_regular_refund``
reduces to the full per-authorization intrinsic less the state
refill, and the exact result is asserted via ``expected_receipt``.
``k`` is the number of authorizations that return the regular
account-write charge. With no EVM execution,
``gas_before_regular_refund`` reduces to the full per-authorization
intrinsic less the state refill, and the exact result is asserted
via ``expected_receipt``.

Each ``invalidity`` kind (``INVALID_NONCE``, ``INVALID_CHAIN_ID``,
``REPEATED_NONCE``, ``AUTHORITY_IS_CONTRACT``) yields one valid and
one invalid tuple, so ``n = 2`` and ``k = 1`` uniformly and every
one invalid tuple, so ``n = 2`` and ``k = 2`` uniformly and every
kind pins the same receipt gas. This is the numeric-receipt
companion to ``test_invalid_auth_charged_intrinsic`` (which asserts
only post state).
Expand Down Expand Up @@ -418,30 +419,33 @@ def test_mixed_validity_multi_auth_receipt_gas(
raise ValueError(f"unknown invalidity: {invalidity!r}")

n = len(authorization_list)
refundable = 1 # exactly one valid, existing-leaf authorization
regular_refundable = 2

total_intrinsic = fork.transaction_intrinsic_cost_calculator()(
authorization_list_or_count=n,
)
intrinsic_state = fork.transaction_intrinsic_state_gas(
authorization_count=n,
)
# Only the valid existing-leaf authorization refills the state
# channel (NEW_ACCOUNT); the invalid tuple refunds nothing. The
# refill is subtracted first and is not subject to the one-fifth cap.
state_refund = gas_costs.REFUND_AUTH_PER_EXISTING_ACCOUNT * refundable
# The valid existing-leaf authorization refills NEW_ACCOUNT. The
# invalid skipped tuple refills the full per-auth state intrinsic.
# State refills are subtracted first and are not subject to the
# one-fifth cap.
state_refund = gas_costs.REFUND_AUTH_PER_EXISTING_ACCOUNT + (
intrinsic_state // n
)

# No EVM execution (the target is a STOP), so the regular and state
# execution gas are both zero and ``gas_before_regular_refund``
# reduces to the full per-auth intrinsic less the state refill.
gas_before_regular_refund = total_intrinsic - state_refund
regular_refund = min(
refundable * account_write,
regular_refundable * account_write,
gas_before_regular_refund // fork.max_refund_quotient(),
)
# With only a single refundable authorization the one-fifth cap is
# generous, so the full ACCOUNT_WRITE clears on the regular channel.
assert regular_refund == refundable * account_write
# The one-fifth cap is generous, so both ACCOUNT_WRITE refunds clear
# on the regular channel.
assert regular_refund == regular_refundable * account_write
cumulative_gas_used = gas_before_regular_refund - regular_refund

tx = Transaction(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

Ported from:
state_tests/stCreate2/create2checkFieldsInInitcodeFiller.json
@manually-enhanced: Do not overwrite. The env `gas_limit` is omitted so
the framework default supplies ample gas for EIP-8037 state accounting.
"""

import pytest
Expand Down Expand Up @@ -115,7 +117,6 @@ def test_create2check_fields_in_initcode(
timestamp=1000,
prev_randao=0x20000,
base_fee_per_gas=10,
gas_limit=1000000,
)

pre[sender] = Account(balance=0x56BC75E2D63100000)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

Ported from:
state_tests/Shanghai/stEIP3651_warmcoinbase/coinbaseWarmAccountCallGasFiller.yml
@manually-enhanced: Do not overwrite. When EIP-8038 is enabled,
EXTCODESIZE and EXTCODECOPY charge an extra warm code-read.
"""

import pytest
Expand Down Expand Up @@ -278,6 +280,11 @@ def test_coinbase_warm_account_call_gas(
nonce=1,
)

post = {target: Account(storage={0: 100})}
warm_access = fork.gas_costs().WARM_ACCESS
# EIP-8038 charges EXTCODESIZE (d0) and EXTCODECOPY (d1) a second
# WARM_ACCESS for the account code read; other opcodes are unchanged.
ext_code_read = d in (0, 1) and fork.is_eip_enabled(8038)
expected_gas = warm_access + (warm_access if ext_code_read else 0)
post = {target: Account(storage={0: expected_gas})}

state_test(env=env, pre=pre, post=post, tx=tx)
Loading