Skip to content

Commit f23e4ab

Browse files
pdobaczmarioevz
andauthored
feat(tests): Add missing OOG tests: EXP, LOG and constant gas generally (#1686)
* feat(tests): Backfill tests for OOGing on constant gas * feat(tests): Add tests for OOGing in EXP variable gas * feat(tests): Add tests for OOGing in LOGx variable gas * feat(testing/tools): Enshrine gas_test * refactor(testing/tools): Small changes * fix(testing/tools): Typo * fix(testing/tools): Remove env dep * fix(tests): tests using gas_test * fix(tox): Lint --------- Co-authored-by: Mario Vega <[email protected]>
1 parent b105c23 commit f23e4ab

File tree

11 files changed

+528
-10
lines changed

11 files changed

+528
-10
lines changed

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Test fixtures for use by clients are available for each release on the [Github r
3939
- ✨ Add tests for ecadd/ecmul/ecpairing constant gas repricing ([#1738](https://github.com/ethereum/execution-specs/pull/1738)).
4040
- ✨ Add tests that EIP-1559 and EIP-2930 typed txs are invalid and void before their fork ([#1754](https://github.com/ethereum/execution-specs/pull/1754)).
4141
- ✨ Add tests for an old validation rule for gas limit above 5000 ([#1731](https://github.com/ethereum/execution-specs/pull/1731)).
42+
- ✨ Add tests for OOG in EXP, LOG and others ([#1686](https://github.com/ethereum/execution-specs/pull/1686)).
4243

4344
## [v5.3.0](https://github.com/ethereum/execution-spec-tests/releases/tag/v5.3.0) - 2025-10-09
4445

packages/testing/src/execution_testing/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
Switch,
9696
While,
9797
extend_with_defaults,
98+
gas_test,
9899
generate_system_contract_deploy_test,
99100
generate_system_contract_error_test,
100101
)
@@ -207,6 +208,7 @@
207208
"compute_create2_address",
208209
"compute_eofcreate_address",
209210
"extend_with_defaults",
211+
"gas_test",
210212
"generate_system_contract_deploy_test",
211213
"generate_system_contract_error_test",
212214
"keccak256",

packages/testing/src/execution_testing/tools/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
)
1515
from .utility.generators import (
1616
DeploymentTestType,
17+
gas_test,
1718
generate_system_contract_deploy_test,
1819
generate_system_contract_error_test,
1920
)
@@ -31,6 +32,7 @@
3132
"Switch",
3233
"While",
3334
"extend_with_defaults",
35+
"gas_test",
3436
"generate_system_contract_deploy_test",
3537
"generate_system_contract_error_test",
3638
"get_current_commit_hash_or_tag",

packages/testing/src/execution_testing/tools/utility/generators.py

Lines changed: 190 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Test generator decorators."""
22

3+
import itertools
34
import json
45
from enum import StrEnum
56
from pathlib import Path
@@ -9,8 +10,8 @@
910

1011
from execution_testing.base_types import Account, Address, Hash
1112
from execution_testing.exceptions import BlockException
12-
from execution_testing.forks import Fork
13-
from execution_testing.specs import BlockchainTestFiller
13+
from execution_testing.forks import Berlin, Fork
14+
from execution_testing.specs import BlockchainTestFiller, StateTestFiller
1415
from execution_testing.specs.blockchain import Block
1516
from execution_testing.test_types import Alloc, Transaction
1617
from execution_testing.vm import Bytecode, Op
@@ -425,3 +426,190 @@ def wrapper(
425426
return wrapper
426427

427428
return decorator
429+
430+
431+
"""Storage addresses for common testing fields"""
432+
_slot = itertools.count()
433+
slot_cold_gas = next(_slot)
434+
slot_warm_gas = next(_slot)
435+
slot_oog_call_result = next(_slot)
436+
slot_sanity_call_result = next(_slot)
437+
438+
LEGACY_CALL_FAILURE = 0
439+
LEGACY_CALL_SUCCESS = 1
440+
441+
442+
def gas_test(
443+
*,
444+
fork: Fork,
445+
state_test: StateTestFiller,
446+
pre: Alloc,
447+
setup_code: Bytecode,
448+
subject_code: Bytecode,
449+
tear_down_code: Bytecode | None = None,
450+
cold_gas: int,
451+
warm_gas: int | None = None,
452+
subject_address: Address | None = None,
453+
subject_balance: int = 0,
454+
oog_difference: int = 1,
455+
out_of_gas_testing: bool = True,
456+
prelude_code: Bytecode | None = None,
457+
tx_gas: int | None = None,
458+
) -> None:
459+
"""
460+
Create State Test to check the gas cost of a sequence of EOF code.
461+
462+
`setup_code` and `tear_down_code` are called multiple times during the
463+
test, and MUST NOT have any side-effects which persist across message
464+
calls, and in particular, any effects on the gas usage of `subject_code`.
465+
"""
466+
if fork < Berlin:
467+
raise ValueError(
468+
"Gas tests before Berlin are not supported due to CALL gas changes"
469+
)
470+
471+
if cold_gas <= 0:
472+
raise ValueError(
473+
f"Target gas allocations (cold_gas) must be > 0, got {cold_gas}"
474+
)
475+
if warm_gas is None:
476+
warm_gas = cold_gas
477+
478+
sender = pre.fund_eoa()
479+
if tear_down_code is None:
480+
tear_down_code = Op.STOP
481+
address_baseline = pre.deploy_contract(setup_code + tear_down_code)
482+
code_subject = setup_code + subject_code + tear_down_code
483+
address_subject = pre.deploy_contract(
484+
code_subject,
485+
balance=subject_balance,
486+
address=subject_address,
487+
)
488+
# 2 times GAS, POP, CALL, 6 times PUSH1 - instructions charged for at every
489+
# gas run
490+
gas_costs = fork.gas_costs()
491+
OPCODE_GAS_COST = gas_costs.G_BASE
492+
OPCODE_POP_COST = gas_costs.G_BASE
493+
OPCODE_PUSH_COST = gas_costs.G_VERY_LOW
494+
gas_single_gas_run = (
495+
2 * OPCODE_GAS_COST
496+
+ OPCODE_POP_COST
497+
+ gas_costs.G_WARM_ACCOUNT_ACCESS
498+
+ 6 * OPCODE_PUSH_COST
499+
)
500+
address_legacy_harness = pre.deploy_contract(
501+
code=(
502+
# warm subject and baseline without executing
503+
(
504+
Op.BALANCE(address_subject)
505+
+ Op.POP
506+
+ Op.BALANCE(address_baseline)
507+
+ Op.POP
508+
)
509+
# run any "prelude" code that may have universal side effects
510+
+ prelude_code
511+
# Baseline gas run
512+
+ (
513+
Op.GAS
514+
+ Op.CALL(address=address_baseline, gas=Op.GAS)
515+
+ Op.POP
516+
+ Op.GAS
517+
+ Op.SWAP1
518+
+ Op.SUB
519+
)
520+
# cold gas run
521+
+ (
522+
Op.GAS
523+
+ Op.CALL(address=address_subject, gas=Op.GAS)
524+
+ Op.POP
525+
+ Op.GAS
526+
+ Op.SWAP1
527+
+ Op.SUB
528+
)
529+
# warm gas run
530+
+ (
531+
Op.GAS
532+
+ Op.CALL(address=address_subject, gas=Op.GAS)
533+
+ Op.POP
534+
+ Op.GAS
535+
+ Op.SWAP1
536+
+ Op.SUB
537+
)
538+
# Store warm gas: DUP3 is the gas of the baseline gas run
539+
+ (
540+
Op.DUP3
541+
+ Op.SWAP1
542+
+ Op.SUB
543+
+ Op.PUSH2(slot_warm_gas)
544+
+ Op.SSTORE
545+
)
546+
# store cold gas: DUP2 is the gas of the baseline gas run
547+
+ (
548+
Op.DUP2
549+
+ Op.SWAP1
550+
+ Op.SUB
551+
+ Op.PUSH2(slot_cold_gas)
552+
+ Op.SSTORE
553+
)
554+
+ (
555+
(
556+
# do an oog gas run, unless skipped with
557+
# `out_of_gas_testing=False`:
558+
#
559+
# - DUP7 is the gas of the baseline gas run, after other
560+
# CALL args were pushed
561+
# - subtract the gas charged by the harness
562+
# - add warm gas charged by the subject
563+
# - subtract `oog_difference` to cause OOG exception
564+
# (1 by default)
565+
Op.SSTORE(
566+
slot_oog_call_result,
567+
Op.CALL(
568+
gas=Op.ADD(
569+
warm_gas - gas_single_gas_run - oog_difference,
570+
Op.DUP7,
571+
),
572+
address=address_subject,
573+
),
574+
)
575+
# sanity gas run: not subtracting 1 to see if enough gas
576+
# makes the call succeed
577+
+ Op.SSTORE(
578+
slot_sanity_call_result,
579+
Op.CALL(
580+
gas=Op.ADD(warm_gas - gas_single_gas_run, Op.DUP7),
581+
address=address_subject,
582+
),
583+
)
584+
+ Op.STOP
585+
)
586+
if out_of_gas_testing
587+
else Op.STOP
588+
)
589+
),
590+
)
591+
592+
post = {
593+
address_legacy_harness: Account(
594+
storage={
595+
slot_warm_gas: warm_gas,
596+
slot_cold_gas: cold_gas,
597+
},
598+
),
599+
}
600+
601+
if out_of_gas_testing:
602+
post[address_legacy_harness].storage[slot_oog_call_result] = (
603+
LEGACY_CALL_FAILURE
604+
)
605+
post[address_legacy_harness].storage[slot_sanity_call_result] = (
606+
LEGACY_CALL_SUCCESS
607+
)
608+
609+
if tx_gas is None:
610+
tx_gas = gas_single_gas_run + cold_gas + 500_000
611+
tx = Transaction(
612+
to=address_legacy_harness, gas_limit=tx_gas, sender=sender
613+
)
614+
615+
state_test(pre=pre, tx=tx, post=post)

0 commit comments

Comments
 (0)