Skip to content

Commit 55ee6b1

Browse files
authored
fix(tests): EIP-8037 unmask intrinsic-cap transaction-validity checks (#2956)
1 parent 4d47a44 commit 55ee6b1

2 files changed

Lines changed: 175 additions & 57 deletions

File tree

tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -148,24 +148,30 @@ def test_calldata_floor_exceeding_tx_gas_limit_cap(
148148
exceeds_cap: bool,
149149
) -> None:
150150
"""
151-
Verify calldata floor > TX_MAX_GAS_LIMIT rejects the transaction.
151+
Reject a transaction whose calldata floor exceeds the cap, isolating
152+
the cap check from the sufficiency check.
152153
153-
When the EIP-7623 calldata floor cost exceeds the EIP-7825 transaction
154-
gas limit cap, the transaction must be rejected at validation —
155-
even though the regular intrinsic gas may be within the cap.
154+
EIP-8037 caps ``max(intrinsic_regular, calldata_floor)`` at
155+
``TX_MAX_GAS_LIMIT``. When the EIP-7976 calldata floor crosses the cap
156+
the transaction must be rejected even though the regular intrinsic gas
157+
is within the cap. For the rejection case ``gas_limit`` is set above the
158+
floor so the sufficiency check ``max(intrinsic_total, floor) <= tx.gas``
159+
passes and the cap is the only reason for rejection — the exact shape a
160+
client with the sufficiency gate but no cap gate would wrongly execute.
156161
157162
at_cap: tightest calldata floor that fits within the cap —
158163
transaction accepted.
159-
exceeds_cap: one zero byte more tips the floor over the cap —
164+
exceeds_cap: one byte more tips the floor over the cap —
160165
transaction rejected.
161166
"""
162167
gas_costs = fork.gas_costs()
163-
gas_limit_cap = fork.transaction_gas_limit_cap()
164-
assert gas_limit_cap is not None
168+
cap = fork.transaction_gas_limit_cap()
169+
assert cap is not None
170+
floor_cost = fork.transaction_data_floor_cost_calculator()
165171

166172
floor_token = gas_costs.TX_DATA_TOKEN_FLOOR
167173
tx_base = gas_costs.TX_BASE
168-
max_tokens = (gas_limit_cap - tx_base) // floor_token
174+
max_tokens = (cap - tx_base) // floor_token
169175

170176
if fork.is_eip_enabled(7976):
171177
# EIP-7976: all bytes contribute 4 floor tokens regardless of
@@ -183,12 +189,29 @@ def test_calldata_floor_exceeding_tx_gas_limit_cap(
183189
if exceeds_cap:
184190
zero_bytes += 1
185191
calldata = b"\x01" * nonzero_bytes + b"\x00" * zero_bytes
192+
186193
contract = pre.deploy_contract(Op.STOP)
194+
floor = floor_cost(data=calldata)
195+
196+
if exceeds_cap:
197+
intrinsic = fork.transaction_intrinsic_cost_calculator()
198+
regular = intrinsic(
199+
calldata=calldata,
200+
return_cost_deducted_prior_execution=True,
201+
)
202+
assert floor > cap, "calldata floor must exceed the cap"
203+
assert regular < cap, "regular intrinsic must stay below the cap"
204+
# Fund the floor in full so the sufficiency check cannot reject the
205+
# transaction first; only the cap check can.
206+
gas_limit = floor + 1_000_000
207+
else:
208+
assert floor <= cap
209+
gas_limit = cap
187210

188211
tx = Transaction(
189212
to=contract,
190213
data=calldata,
191-
gas_limit=gas_limit_cap,
214+
gas_limit=gas_limit,
192215
sender=pre.fund_eoa(),
193216
error=TransactionException.INTRINSIC_GAS_TOO_LOW
194217
if exceeds_cap

tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py

Lines changed: 143 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
import pytest
1818
from execution_testing import (
19+
AccessList,
1920
Account,
21+
Address,
2022
Alloc,
2123
AuthorizationTuple,
2224
Environment,
@@ -29,7 +31,7 @@
2931
)
3032
from execution_testing.checklists import EIPChecklist
3133

32-
from .spec import Spec, ref_spec_8037
34+
from .spec import ref_spec_8037
3335

3436
REFERENCE_SPEC_GIT_PATH = ref_spec_8037.git_path
3537
REFERENCE_SPEC_VERSION = ref_spec_8037.version
@@ -266,6 +268,36 @@ def test_refund_with_reservoir_state_gas(
266268
state_test(env=env, pre=pre, post=post, tx=tx)
267269

268270

271+
def _access_list_over_regular_cap(
272+
fork: Fork, cap: int, *, margin_num: int = 1, margin_den: int = 1
273+
) -> list[AccessList]:
274+
"""
275+
Build an access list whose intrinsic *regular* gas exceeds ``cap`` by
276+
roughly the factor ``margin_num / margin_den``.
277+
278+
Each access-list address adds a fixed amount to the regular intrinsic
279+
(the EIP-2930 address cost plus the EIP-7981 floor-token surcharge) and
280+
a much smaller amount to the calldata floor, so the list raises the
281+
regular operand of ``max(intrinsic_regular, calldata_floor)`` over the
282+
cap while the floor stays below it. No state gas is incurred.
283+
"""
284+
intrinsic = fork.transaction_intrinsic_cost_calculator()
285+
base_regular = intrinsic(return_cost_deducted_prior_execution=True)
286+
per_address_regular = (
287+
intrinsic(
288+
access_list=[AccessList(address=Address(0x100), storage_keys=[])],
289+
return_cost_deducted_prior_execution=True,
290+
)
291+
- base_regular
292+
)
293+
assert per_address_regular > 0
294+
num_entries = (cap * margin_num) // (per_address_regular * margin_den) + 1
295+
return [
296+
AccessList(address=Address(0x10000 + i), storage_keys=[])
297+
for i in range(num_entries)
298+
]
299+
300+
269301
@pytest.mark.exception_test
270302
@pytest.mark.valid_from("EIP8037")
271303
def test_intrinsic_regular_gas_exceeds_cap(
@@ -274,30 +306,43 @@ def test_intrinsic_regular_gas_exceeds_cap(
274306
fork: Fork,
275307
) -> None:
276308
"""
277-
Test that tx is rejected when intrinsic regular gas exceeds cap.
278-
279-
validate_transaction checks that the intrinsic regular gas (or
280-
calldata floor) does not exceed the transaction gas limit cap.
281-
A transaction with enough calldata to push intrinsic cost above
282-
the cap is invalid even with a high gas_limit.
309+
Reject a transaction whose intrinsic *regular* gas exceeds the cap.
310+
311+
EIP-8037 enforces ``max(intrinsic_regular, calldata_floor) <=
312+
TX_MAX_GAS_LIMIT`` after the separate sufficiency check
313+
``max(intrinsic_total, calldata_floor) <= tx.gas``. A large access list
314+
raises the regular intrinsic over the cap while adding no state gas and
315+
keeping the calldata floor below the cap. ``gas_limit`` is set above the
316+
total intrinsic so the sufficiency check passes and the cap is the only
317+
reason the transaction is rejected; a client that compares the intrinsic
318+
against ``tx.gas`` but never against the cap would wrongly accept it.
283319
"""
284-
gas_costs = fork.gas_costs()
285-
gas_limit_cap = fork.transaction_gas_limit_cap()
286-
assert gas_limit_cap is not None
287-
# One more non-zero byte than needed to exceed the cap
288-
calldata_len = gas_limit_cap // gas_costs.TX_DATA_PER_NON_ZERO + 1
289-
calldata = b"\x01" * calldata_len
320+
cap = fork.transaction_gas_limit_cap()
321+
assert cap is not None
322+
floor_cost = fork.transaction_data_floor_cost_calculator()
323+
intrinsic = fork.transaction_intrinsic_cost_calculator()
324+
325+
access_list = _access_list_over_regular_cap(fork, cap)
326+
regular = intrinsic(
327+
access_list=access_list,
328+
return_cost_deducted_prior_execution=True,
329+
)
330+
floor = floor_cost(data=b"", access_list=access_list)
331+
state = fork.transaction_intrinsic_state_gas()
332+
tx_gas = regular + state + 1_000_000
290333

291-
contract = pre.deploy_contract(code=Op.STOP)
334+
assert max(regular, floor) > cap, "cap check must fire"
335+
assert regular + state <= tx_gas, "sufficiency check must not fire"
336+
assert floor <= tx_gas
292337

293338
tx = Transaction(
294-
to=contract,
295-
gas_limit=gas_limit_cap * 2,
296-
data=calldata,
339+
ty=1,
340+
to=pre.deploy_contract(code=Op.STOP),
341+
gas_limit=tx_gas,
342+
access_list=access_list,
297343
sender=pre.fund_eoa(),
298344
error=TransactionException.INTRINSIC_GAS_TOO_LOW,
299345
)
300-
301346
state_test(pre=pre, post={}, tx=tx)
302347

303348

@@ -309,46 +354,96 @@ def test_intrinsic_regular_gas_exceeds_cap_with_floor_below_cap(
309354
fork: Fork,
310355
) -> None:
311356
"""
312-
Test rejection when intrinsic regular gas exceeds the per-tx gas
313-
cap while the calldata floor stays below the cap.
314-
315-
EIP-7825/8037 applies the cap to both intrinsic dimensions
316-
independently. The companion `test_intrinsic_regular_gas_exceeds_cap`
317-
pushes both dimensions above the cap with non-zero calldata, so an
318-
implementation that only checks `max(regular, floor)` against the
319-
cap would still pass. This test isolates the regular-only case via
320-
a large EIP-7702 authorization list and minimal calldata.
357+
Reject when intrinsic *regular* gas exceeds the cap while the calldata
358+
floor stays below it, isolating the regular operand of
359+
``max(intrinsic_regular, calldata_floor)``.
360+
361+
A large access list with no calldata pushes the regular intrinsic over
362+
the cap while the floor stays well below it, and ``gas_limit`` covers
363+
the total intrinsic so the sufficiency check passes. The explicit
364+
``floor < cap`` assertion guarantees the rejection comes from the
365+
regular operand, so a client that compares only the calldata floor
366+
against the cap would wrongly accept the transaction.
321367
"""
322-
gas_limit_cap = fork.transaction_gas_limit_cap()
323-
assert gas_limit_cap is not None
368+
cap = fork.transaction_gas_limit_cap()
369+
assert cap is not None
370+
floor_cost = fork.transaction_data_floor_cost_calculator()
371+
intrinsic = fork.transaction_intrinsic_cost_calculator()
324372

325-
# Authorizations contribute to regular intrinsic only (not floor).
326-
# Pick enough to push regular > cap by a comfortable margin.
327-
auth_count = (gas_limit_cap // Spec.PER_AUTH_BASE_COST) + 1
328-
calldata = b"\x01" * 4 # tiny: floor stays << cap.
329-
330-
target = pre.deploy_contract(code=Op.STOP)
331-
authorizations = [
332-
AuthorizationTuple(
333-
address=target,
334-
nonce=0,
335-
signer=pre.fund_eoa(),
336-
)
337-
for _ in range(auth_count)
338-
]
373+
access_list = _access_list_over_regular_cap(
374+
fork, cap, margin_num=5, margin_den=4
375+
)
376+
regular = intrinsic(
377+
access_list=access_list,
378+
return_cost_deducted_prior_execution=True,
379+
)
380+
floor = floor_cost(data=b"", access_list=access_list)
381+
state = fork.transaction_intrinsic_state_gas()
382+
tx_gas = regular + state + 1_000_000
383+
384+
assert regular > cap, "regular operand must exceed the cap"
385+
assert floor < cap, "calldata floor must stay below the cap"
386+
assert regular + state <= tx_gas, "sufficiency check must not fire"
339387

340388
tx = Transaction(
341-
ty=4,
342-
to=target,
343-
gas_limit=gas_limit_cap * 2,
344-
data=calldata,
345-
authorization_list=authorizations,
389+
ty=1,
390+
to=pre.deploy_contract(code=Op.STOP),
391+
gas_limit=tx_gas,
392+
access_list=access_list,
346393
sender=pre.fund_eoa(),
347394
error=TransactionException.INTRINSIC_GAS_TOO_LOW,
348395
)
349396
state_test(pre=pre, post={}, tx=tx)
350397

351398

399+
@pytest.mark.valid_from("EIP8037")
400+
def test_intrinsic_within_cap_gas_limit_above_cap(
401+
state_test: StateTestFiller,
402+
pre: Alloc,
403+
fork: Fork,
404+
) -> None:
405+
"""
406+
Accept a transaction whose ``gas_limit`` exceeds the cap when both
407+
intrinsic operands stay below it.
408+
409+
EIP-8037 relaxes the EIP-7825 cap on ``tx.gas`` itself; only
410+
``max(intrinsic_regular, calldata_floor)`` is capped. This positive
411+
control sets ``gas_limit`` above the cap with a small access list so
412+
both operands are far below it, and the transaction must execute. It is
413+
the accepting counterpart to the cap-rejection tests above.
414+
"""
415+
cap = fork.transaction_gas_limit_cap()
416+
assert cap is not None
417+
floor_cost = fork.transaction_data_floor_cost_calculator()
418+
intrinsic = fork.transaction_intrinsic_cost_calculator()
419+
420+
access_list = [
421+
AccessList(address=Address(0x10000 + i), storage_keys=[])
422+
for i in range(16)
423+
]
424+
regular = intrinsic(
425+
access_list=access_list,
426+
return_cost_deducted_prior_execution=True,
427+
)
428+
floor = floor_cost(data=b"", access_list=access_list)
429+
assert regular <= cap
430+
assert floor <= cap
431+
432+
storage = Storage()
433+
contract = pre.deploy_contract(
434+
code=Op.SSTORE(storage.store_next(1), 1),
435+
)
436+
437+
tx = Transaction(
438+
ty=1,
439+
to=contract,
440+
gas_limit=cap + 3_000_000,
441+
access_list=access_list,
442+
sender=pre.fund_eoa(),
443+
)
444+
state_test(pre=pre, post={contract: Account(storage=storage)}, tx=tx)
445+
446+
352447
@pytest.mark.parametrize(
353448
"above_floor",
354449
[

0 commit comments

Comments
 (0)