Skip to content

Commit 88fa302

Browse files
committed
feat(tests): 8037 block 2D gas accounting edge cases
Ported from closed PR ethereum#2639. Adds four tests that exercise scenarios the existing 2D accounting tests don't cover: test_tx_rejected_when_regular_gas_exceeds_block_limit_small Complements `test_block_regular_gas_limit`. Uses a tight block gas_limit (2 * intrinsic) and a rejected tx sized just one gas above the remaining regular budget. Catches clients that only handle the TX_MAX-sized case. test_block_2d_gas_tx_gas_limit_exceeds_regular_remaining Parametrized over `tx_gas_limit_equals_block_limit` and `tx_gas_limit_just_above_remaining`. A preceding STOP tx consumes regular gas, then a second tx has `gas_limit >> TX_MAX_GAS_LIMIT`. The pre-execution check must use `min(TX_MAX_GAS_LIMIT, tx.gas - intrinsic.state)` not the raw `tx.gas_limit`; clients that subtract the full gas_limit reject this valid block. test_receipt_cumulative_differs_from_header_gas_used Explicit assertion that 2D `header.gas_used` can diverge from 1D receipt `cumulative_gas_used` when state dominates. Catches clients that mix up the two values. test_failed_create_tx_state_gas_dominates (parametrized `revert`, `halt`) Creation tx (to=None) with tight gas where initcode fails. Intrinsic state gas (GAS_NEW_ACCOUNT) is preserved across the top-level failure refund; tight regular budget keeps block_regular below `create_state_gas` so the state dimension dominates the header. Complements PR ethereum#2689's `test_creation_tx_failure_preserves_intrinsic_state_gas` by covering the REVERT path and the tight-gas scenario.
1 parent 80672f7 commit 88fa302

2 files changed

Lines changed: 228 additions & 0 deletions

File tree

tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
Op,
2323
Storage,
2424
Transaction,
25+
TransactionException,
2526
)
2627

2728
from .spec import ref_spec_8037
@@ -466,3 +467,169 @@ def test_multi_block_dimension_flip(
466467
],
467468
post=post_2,
468469
)
470+
471+
472+
@pytest.mark.exception_test
473+
@pytest.mark.valid_from("EIP8037")
474+
def test_tx_rejected_when_regular_gas_exceeds_block_limit_small(
475+
blockchain_test: BlockchainTestFiller,
476+
pre: Alloc,
477+
fork: Fork,
478+
) -> None:
479+
"""
480+
Reject a small-gas tx whose regular contribution overflows the block.
481+
482+
Complements `test_block_regular_gas_limit` which covers
483+
`TX_MAX_GAS_LIMIT`-sized transactions. Here the block has a
484+
tight gas_limit (2 * intrinsic) and the second tx's gas_limit
485+
sits just one gas above the remaining regular budget, so the
486+
pre-execution check rejects it without executing.
487+
"""
488+
gas_limit_cap = fork.transaction_gas_limit_cap()
489+
assert gas_limit_cap is not None
490+
intrinsic_gas = fork.transaction_intrinsic_cost_calculator()()
491+
492+
block_gas_limit = intrinsic_gas * 2
493+
494+
filler = pre.deploy_contract(code=Op.STOP)
495+
filler_tx = Transaction(
496+
to=filler,
497+
gas_limit=intrinsic_gas,
498+
sender=pre.fund_eoa(),
499+
)
500+
501+
rejected_gas_limit = intrinsic_gas + 1
502+
assert rejected_gas_limit < gas_limit_cap
503+
rejected = pre.deploy_contract(code=Op.STOP)
504+
rejected_tx = Transaction(
505+
to=rejected,
506+
gas_limit=rejected_gas_limit,
507+
sender=pre.fund_eoa(),
508+
error=TransactionException.GAS_ALLOWANCE_EXCEEDED,
509+
)
510+
511+
blockchain_test(
512+
genesis_environment=Environment(gas_limit=block_gas_limit),
513+
pre=pre,
514+
blocks=[
515+
Block(
516+
txs=[filler_tx, rejected_tx],
517+
gas_limit=block_gas_limit,
518+
exception=TransactionException.GAS_ALLOWANCE_EXCEEDED,
519+
)
520+
],
521+
post={},
522+
)
523+
524+
525+
@pytest.mark.parametrize(
526+
"tx2_gas_limit_equals_block_gas_limit",
527+
[
528+
pytest.param(True, id="tx_gas_limit_equals_block_limit"),
529+
pytest.param(False, id="tx_gas_limit_just_above_remaining"),
530+
],
531+
)
532+
@pytest.mark.valid_from("EIP8037")
533+
def test_block_2d_gas_tx_gas_limit_exceeds_regular_remaining(
534+
blockchain_test: BlockchainTestFiller,
535+
pre: Alloc,
536+
fork: Fork,
537+
tx2_gas_limit_equals_block_gas_limit: bool,
538+
) -> None:
539+
"""
540+
Verify block validity when tx.gas_limit exceeds regular remaining.
541+
542+
After a preceding STOP tx consumes regular gas, the second tx
543+
has `gas_limit >> TX_MAX_GAS_LIMIT`. The pre-execution inclusion
544+
check must use `min(TX_MAX_GAS_LIMIT, tx.gas - intrinsic.state)`
545+
against the cumulative regular budget, not the raw tx.gas_limit.
546+
A client that subtracts the full `tx.gas_limit` from the regular
547+
pool would reject this otherwise-valid block.
548+
"""
549+
gas_limit_cap = fork.transaction_gas_limit_cap()
550+
assert gas_limit_cap is not None
551+
intrinsic_gas = fork.transaction_intrinsic_cost_calculator()()
552+
env = Environment()
553+
block_gas_limit = int(env.gas_limit)
554+
555+
if tx2_gas_limit_equals_block_gas_limit:
556+
tx2_gas_limit = block_gas_limit
557+
else:
558+
tx2_gas_limit = block_gas_limit - intrinsic_gas + 1
559+
560+
assert tx2_gas_limit > gas_limit_cap
561+
assert tx2_gas_limit > block_gas_limit - intrinsic_gas
562+
563+
stop_contract = pre.deploy_contract(code=Op.STOP)
564+
565+
storage = Storage()
566+
sstore_contract = pre.deploy_contract(
567+
code=Op.SSTORE(storage.store_next(1), 1),
568+
)
569+
570+
tx1_regular = intrinsic_gas
571+
tx2_regular, tx2_state = sstore_tx_gas(fork)
572+
expected_gas_used = max(tx1_regular + tx2_regular, tx2_state)
573+
574+
blockchain_test(
575+
pre=pre,
576+
blocks=[
577+
Block(
578+
txs=[
579+
Transaction(
580+
to=stop_contract,
581+
gas_limit=intrinsic_gas,
582+
sender=pre.fund_eoa(),
583+
),
584+
Transaction(
585+
to=sstore_contract,
586+
gas_limit=tx2_gas_limit,
587+
sender=pre.fund_eoa(),
588+
),
589+
],
590+
header_verify=Header(gas_used=expected_gas_used),
591+
),
592+
],
593+
post={sstore_contract: Account(storage=storage)},
594+
)
595+
596+
597+
@pytest.mark.valid_from("EIP8037")
598+
def test_receipt_cumulative_differs_from_header_gas_used(
599+
blockchain_test: BlockchainTestFiller,
600+
pre: Alloc,
601+
fork: Fork,
602+
) -> None:
603+
"""
604+
Verify receipt cumulative_gas_used can diverge from header gas_used.
605+
606+
Under 2D accounting, `header.gas_used = max(sum_regular, sum_state)`
607+
while a receipt's `cumulative_gas_used` accumulates per-tx
608+
`regular + state`. In a block dominated by state gas, the
609+
header is strictly less than the receipt cumulative. A client
610+
that uses either value for the other check would reject valid
611+
blocks.
612+
"""
613+
tx_regular, tx_state = sstore_tx_gas(fork)
614+
num_txs = 3
615+
616+
txs, post = sstore_txs(pre, fork, num_txs)
617+
618+
block_regular = num_txs * tx_regular
619+
block_state = num_txs * tx_state
620+
header_gas_used = max(block_regular, block_state)
621+
receipt_cumulative = num_txs * (tx_regular + tx_state)
622+
623+
assert block_state > block_regular
624+
assert header_gas_used < receipt_cumulative
625+
626+
blockchain_test(
627+
pre=pre,
628+
blocks=[
629+
Block(
630+
txs=txs,
631+
header_verify=Header(gas_used=header_gas_used),
632+
),
633+
],
634+
post=post,
635+
)

tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1533,3 +1533,64 @@ def test_create_code_deposit_oog_refunds_state_gas(
15331533
)
15341534

15351535
state_test(pre=pre, post={factory: Account(storage=storage)}, tx=tx)
1536+
1537+
1538+
@pytest.mark.parametrize(
1539+
"init_code",
1540+
[
1541+
pytest.param(Op.REVERT(0, 0), id="revert"),
1542+
pytest.param(Op.INVALID, id="halt"),
1543+
],
1544+
)
1545+
@pytest.mark.valid_from("EIP8037")
1546+
def test_failed_create_tx_state_gas_dominates(
1547+
blockchain_test: BlockchainTestFiller,
1548+
pre: Alloc,
1549+
fork: Fork,
1550+
init_code: Bytecode,
1551+
) -> None:
1552+
"""
1553+
Verify 2D header gas_used when a failed creation tx's state dominates.
1554+
1555+
A creation tx (to=None) with a tight gas limit whose initcode
1556+
fails via REVERT or INVALID. The intrinsic state gas for the
1557+
new account is preserved across the top-level failure refund
1558+
(only execution state gas is zeroed). With a tight regular
1559+
budget the state dimension dominates and the header must
1560+
equal `create_state_gas`. A client using 1D sum-based
1561+
accounting would produce a different header value.
1562+
1563+
Complements PR #2689's
1564+
`test_creation_tx_failure_preserves_intrinsic_state_gas` by
1565+
exercising the REVERT path and the tight-gas scenario.
1566+
"""
1567+
intrinsic_calc = fork.transaction_intrinsic_cost_calculator()
1568+
create_state_gas = fork.create_state_gas(code_size=0)
1569+
1570+
intrinsic_total = intrinsic_calc(
1571+
calldata=bytes(init_code), contract_creation=True
1572+
)
1573+
intrinsic_regular = intrinsic_total - create_state_gas
1574+
gas_limit = intrinsic_total + 1000
1575+
1576+
assert intrinsic_regular + 1000 < create_state_gas, (
1577+
"tight gas budget must keep block_regular below create_state_gas"
1578+
)
1579+
1580+
tx = Transaction(
1581+
to=None,
1582+
data=init_code,
1583+
gas_limit=gas_limit,
1584+
sender=pre.fund_eoa(),
1585+
)
1586+
1587+
blockchain_test(
1588+
pre=pre,
1589+
blocks=[
1590+
Block(
1591+
txs=[tx],
1592+
header_verify=Header(gas_used=create_state_gas),
1593+
),
1594+
],
1595+
post={},
1596+
)

0 commit comments

Comments
 (0)