Skip to content

Commit d178bdb

Browse files
committed
feat(filler): Add gas optimization flags
1 parent 7b1d0bd commit d178bdb

File tree

2 files changed

+83
-24
lines changed

2 files changed

+83
-24
lines changed

src/pytest_plugins/filler/filler.py

Lines changed: 82 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import configparser
1010
import datetime
11+
import json
1112
import os
1213
import warnings
1314
from enum import Enum
@@ -18,6 +19,7 @@
1819
import xdist
1920
from _pytest.compat import NotSetType
2021
from _pytest.terminal import TerminalReporter
22+
from filelock import FileLock
2123
from pytest_metadata.plugin import metadata_key # type: ignore
2224

2325
from cli.gen_index import generate_fixtures_index
@@ -36,6 +38,7 @@
3638
)
3739
from ethereum_test_forks import Fork, get_transition_fork_predecessor, get_transition_forks
3840
from ethereum_test_specs import BaseTest
41+
from ethereum_test_specs.base import OpMode
3942
from ethereum_test_tools.utility.versioning import (
4043
generate_github_url,
4144
get_current_commit_hash_or_tag,
@@ -280,6 +283,44 @@ def pytest_addoption(parser: pytest.Parser):
280283
),
281284
)
282285

286+
optimize_gas_group = parser.getgroup(
287+
"optimize gas",
288+
"Arguments defining test gas optimization behavior.",
289+
)
290+
optimize_gas_group.addoption(
291+
"--optimize-gas",
292+
action="store_true",
293+
dest="optimize_gas",
294+
default=False,
295+
help=(
296+
"Attempt to optimize the gas used in every transaction for the filled tests, "
297+
"then print the minimum amount of gas at which the test still produces a correct "
298+
"post state and the exact same trace."
299+
),
300+
)
301+
optimize_gas_group.addoption(
302+
"--optimize-gas-output",
303+
action="store",
304+
dest="optimize_gas_output",
305+
default=Path("optimize-gas-output.json"),
306+
type=Path,
307+
help=(
308+
"Path to the JSON file that is output to the gas optimization. "
309+
"Requires `--optimize-gas`."
310+
),
311+
)
312+
optimize_gas_group.addoption(
313+
"--optimize-gas-post-processing",
314+
action="store_true",
315+
dest="optimize_gas_post_processing",
316+
default=False,
317+
help=(
318+
"Post process the traces during gas optimization in order to Account for "
319+
"opcodes that put the current gas in the stack, in order to remove "
320+
"remaining-gas from the comparison."
321+
),
322+
)
323+
283324
debug_group = parser.getgroup("debug", "Arguments defining debug behavior")
284325
debug_group.addoption(
285326
"--evm-dump-dir",
@@ -359,16 +400,25 @@ def pytest_configure(config):
359400
):
360401
config.option.htmlpath = config.fixture_output.directory / default_html_report_file_path()
361402

403+
config.gas_optimized_tests = {}
404+
if config.getoption("optimize_gas", False):
405+
if config.getoption("optimize_gas_post_processing"):
406+
config.op_mode = OpMode.OPTIMIZE_GAS_POST_PROCESSING
407+
else:
408+
config.op_mode = OpMode.OPTIMIZE_GAS
409+
410+
config.collect_traces = config.getoption("evm_collect_traces") or config.getoption(
411+
"optimize_gas", False
412+
)
413+
362414
# Instantiate the transition tool here to check that the binary path/trace option is valid.
363415
# This ensures we only raise an error once, if appropriate, instead of for every test.
364416
evm_bin = config.getoption("evm_bin")
365417
if evm_bin is None:
366418
assert TransitionTool.default_tool is not None, "No default transition tool found"
367-
t8n = TransitionTool.default_tool(trace=config.getoption("evm_collect_traces"))
419+
t8n = TransitionTool.default_tool(trace=config.collect_traces)
368420
else:
369-
t8n = TransitionTool.from_binary_path(
370-
binary_path=evm_bin, trace=config.getoption("evm_collect_traces")
371-
)
421+
t8n = TransitionTool.from_binary_path(binary_path=evm_bin, trace=config.collect_traces)
372422
if (
373423
isinstance(config.getoption("numprocesses"), int)
374424
and config.getoption("numprocesses") > 0
@@ -584,9 +634,7 @@ def t8n(
584634
request: pytest.FixtureRequest, evm_bin: Path | None, t8n_server_url: str | None
585635
) -> Generator[TransitionTool, None, None]:
586636
"""Return configured transition tool."""
587-
kwargs = {
588-
"trace": request.config.getoption("evm_collect_traces"),
589-
}
637+
kwargs = {"trace": request.config.collect_traces} # type: ignore[attr-defined]
590638
if t8n_server_url is not None:
591639
kwargs["server_url"] = t8n_server_url
592640
if evm_bin is None:
@@ -645,7 +693,7 @@ def evm_fixture_verification(
645693
try:
646694
evm_fixture_verification = FixtureConsumerTool.from_binary_path(
647695
binary_path=Path(verify_fixtures_bin),
648-
trace=request.config.getoption("evm_collect_traces"),
696+
trace=request.config.collect_traces, # type: ignore[attr-defined]
649697
)
650698
except Exception:
651699
if reused_evm_bin:
@@ -958,12 +1006,22 @@ def __init__(self, *args, **kwargs):
9581006
)
9591007
group: PreAllocGroup = request.config.pre_alloc_groups[pre_alloc_hash] # type: ignore[annotation-unchecked]
9601008
self.pre = group.pre
961-
962-
fixture = self.generate(
963-
t8n=t8n,
964-
fork=fork,
965-
fixture_format=fixture_format,
966-
)
1009+
try:
1010+
fixture = self.generate(
1011+
t8n=t8n,
1012+
fork=fork,
1013+
fixture_format=fixture_format,
1014+
)
1015+
finally:
1016+
if (
1017+
request.config.op_mode == OpMode.OPTIMIZE_GAS
1018+
or request.config.op_mode == OpMode.OPTIMIZE_GAS_POST_PROCESSING
1019+
):
1020+
gas_optimized_tests = request.config.gas_optimized_tests
1021+
assert gas_optimized_tests is not None
1022+
# Force adding something to the list, even if it's None,
1023+
# to keep track of failed tests in the output file.
1024+
gas_optimized_tests[request.node.nodeid] = self._gas_optimization
9671025

9681026
# Post-process for Engine X format (add pre_hash and state diff)
9691027
if (
@@ -1180,6 +1238,16 @@ def pytest_sessionfinish(session: pytest.Session, exitstatus: int):
11801238
session.config.pre_alloc_groups.to_folder(pre_alloc_groups_folder)
11811239
return
11821240

1241+
if session.config.getoption("optimize_gas", False): # type: ignore[attr-defined]
1242+
output_file = Path(session.config.getoption("optimize_gas_output"))
1243+
lock_file_path = output_file.with_suffix(".lock")
1244+
assert hasattr(session.config, "gas_optimized_tests")
1245+
gas_optimized_tests: Dict[str, int] = session.config.gas_optimized_tests
1246+
with FileLock(lock_file_path):
1247+
if output_file.exists():
1248+
gas_optimized_tests = json.loads(output_file.read_text()) | gas_optimized_tests
1249+
output_file.write_text(json.dumps(gas_optimized_tests, indent=2, sort_keys=True))
1250+
11831251
if xdist.is_xdist_worker(session):
11841252
return
11851253

src/pytest_plugins/shared/execute_fill.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,17 @@
11
"""Shared pytest fixtures and hooks for EEST generation modes (fill and execute)."""
22

3-
from enum import StrEnum, unique
43
from typing import List
54

65
import pytest
76

87
from ethereum_test_execution import BaseExecute, LabeledExecuteFormat
98
from ethereum_test_fixtures import BaseFixture, LabeledFixtureFormat
109
from ethereum_test_specs import BaseTest
10+
from ethereum_test_specs.base import OpMode
1111
from ethereum_test_types import EOA, Alloc
1212

1313
from ..spec_version_checker.spec_version_checker import EIPSpecTestItem
1414

15-
16-
@unique
17-
class OpMode(StrEnum):
18-
"""Operation mode for the fill and execute."""
19-
20-
CONSENSUS = "consensus"
21-
BENCHMARKING = "benchmarking"
22-
23-
2415
ALL_FIXTURE_PARAMETERS = {
2516
"genesis_environment",
2617
"env",

0 commit comments

Comments
 (0)