Skip to content

Commit e2f2024

Browse files
committed
refactor(tests/benchmark): Optimize generators usages
1 parent d77d3a4 commit e2f2024

File tree

8 files changed

+148
-313
lines changed

8 files changed

+148
-313
lines changed

src/ethereum_test_benchmark/benchmark_code_generator.py

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,46 +3,42 @@
33
optimized bytecode patterns.
44
"""
55

6+
from dataclasses import dataclass
7+
68
from ethereum_test_base_types import Address
79
from ethereum_test_forks import Fork
810
from ethereum_test_specs.benchmark import BenchmarkCodeGenerator
9-
from ethereum_test_types import Alloc, Transaction
10-
from ethereum_test_vm import Bytecode
11+
from ethereum_test_types import Alloc
1112
from ethereum_test_vm.opcodes import Opcodes as Op
1213

1314

15+
@dataclass(kw_only=True)
1416
class JumpLoopGenerator(BenchmarkCodeGenerator):
1517
"""Generates bytecode that loops execution using JUMP operations."""
1618

17-
def deploy_contracts(self, pre: Alloc, fork: Fork) -> Address:
19+
def deploy_contracts(self, *, pre: Alloc, fork: Fork) -> Address:
1820
"""Deploy the looping contract."""
1921
# Benchmark Test Structure:
2022
# setup + JUMPDEST +
2123
# attack + attack + ... + attack +
2224
# cleanup + JUMP(setup_length)
23-
code = self.generate_repeated_code(self.attack_block, self.setup, self.cleanup, fork)
25+
code = self.generate_repeated_code(
26+
repeated_code=self.attack_block, setup=self.setup, cleanup=self.cleanup, fork=fork
27+
)
2428
self._contract_address = pre.deploy_contract(code=code)
2529
return self._contract_address
2630

27-
def generate_transaction(self, pre: Alloc, gas_limit: int, fork: Fork) -> Transaction:
28-
"""Generate transaction that executes the looping contract."""
29-
if not hasattr(self, "_contract_address"):
30-
self.deploy_contracts(pre, fork)
31-
32-
return Transaction(
33-
to=self._contract_address,
34-
gas_limit=gas_limit,
35-
sender=pre.fund_eoa(),
36-
)
37-
3831

32+
@dataclass(kw_only=True)
3933
class ExtCallGenerator(BenchmarkCodeGenerator):
4034
"""
4135
Generates bytecode that fills the contract to
4236
maximum allowed code size.
4337
"""
4438

45-
def deploy_contracts(self, pre: Alloc, fork: Fork) -> Address:
39+
contract_balance: int = 0
40+
41+
def deploy_contracts(self, *, pre: Alloc, fork: Fork) -> Address:
4642
"""Deploy both target and caller contracts."""
4743
# Benchmark Test Structure:
4844
# There are two contracts:
@@ -56,7 +52,8 @@ def deploy_contracts(self, pre: Alloc, fork: Fork) -> Address:
5652

5753
# Deploy target contract that contains the actual attack block
5854
self._target_contract_address = pre.deploy_contract(
59-
code=self.setup + self.attack_block * max_iterations
55+
code=self.setup + self.attack_block * max_iterations,
56+
balance=self.contract_balance,
6057
)
6158

6259
# Create caller contract that repeatedly calls the target contract
@@ -68,17 +65,8 @@ def deploy_contracts(self, pre: Alloc, fork: Fork) -> Address:
6865
# JUMP(setup_length)
6966
code_sequence = Op.POP(Op.STATICCALL(Op.GAS, self._target_contract_address, 0, 0, 0, 0))
7067

71-
caller_code = self.generate_repeated_code(code_sequence, Bytecode(), self.cleanup, fork)
68+
caller_code = self.generate_repeated_code(
69+
repeated_code=code_sequence, cleanup=self.cleanup, fork=fork
70+
)
7271
self._contract_address = pre.deploy_contract(code=caller_code)
7372
return self._contract_address
74-
75-
def generate_transaction(self, pre: Alloc, gas_limit: int, fork: Fork) -> Transaction:
76-
"""Generate transaction that executes the caller contract."""
77-
if not hasattr(self, "_contract_address"):
78-
self.deploy_contracts(pre, fork)
79-
80-
return Transaction(
81-
to=self._contract_address,
82-
gas_limit=gas_limit,
83-
sender=pre.fund_eoa(),
84-
)

src/ethereum_test_specs/benchmark.py

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import math
44
from abc import ABC, abstractmethod
55
from dataclasses import dataclass, field
6-
from typing import Callable, ClassVar, Dict, Generator, List, Sequence, Type
6+
from typing import Any, Callable, ClassVar, Dict, Generator, List, Sequence, Type
77

88
import pytest
99
from pydantic import ConfigDict, Field
@@ -41,27 +41,44 @@ class BenchmarkCodeGenerator(ABC):
4141
attack_block: Bytecode
4242
setup: Bytecode = field(default_factory=Bytecode)
4343
cleanup: Bytecode = field(default_factory=Bytecode)
44+
tx_kwargs: Dict[str, Any] = field(default_factory=dict)
45+
_contract_address: Address | None = None
4446

4547
@abstractmethod
46-
def deploy_contracts(self, pre: Alloc, fork: Fork) -> Address:
48+
def deploy_contracts(self, *, pre: Alloc, fork: Fork) -> Address:
4749
"""Deploy any contracts needed for the benchmark."""
4850
...
4951

50-
@abstractmethod
51-
def generate_transaction(self, pre: Alloc, gas_limit: int, fork: Fork) -> Transaction:
52-
"""Generate a transaction with the specified gas limit."""
53-
...
52+
def generate_transaction(self, *, pre: Alloc, gas_benchmark_value: int) -> Transaction:
53+
"""Generate transaction that executes the looping contract."""
54+
assert self._contract_address is not None
55+
if "gas_limit" not in self.tx_kwargs:
56+
self.tx_kwargs["gas_limit"] = gas_benchmark_value
57+
58+
return Transaction(
59+
to=self._contract_address,
60+
sender=pre.fund_eoa(),
61+
**self.tx_kwargs,
62+
)
5463

5564
def generate_repeated_code(
56-
self, repeated_code: Bytecode, setup: Bytecode, cleanup: Bytecode, fork: Fork
65+
self,
66+
*,
67+
repeated_code: Bytecode,
68+
setup: Bytecode | None = None,
69+
cleanup: Bytecode | None = None,
70+
fork: Fork,
5771
) -> Bytecode:
5872
"""
5973
Calculate the maximum number of iterations that
6074
can fit in the code size limit.
6175
"""
6276
assert len(repeated_code) > 0, "repeated_code cannot be empty"
6377
max_code_size = fork.max_code_size()
64-
78+
if setup is None:
79+
setup = Bytecode()
80+
if cleanup is None:
81+
cleanup = Bytecode()
6582
overhead = len(setup) + len(Op.JUMPDEST) + len(cleanup) + len(Op.JUMP(len(setup)))
6683
available_space = max_code_size - overhead
6784
max_iterations = available_space // len(repeated_code)
@@ -87,7 +104,7 @@ class BenchmarkTest(BaseTest):
87104

88105
model_config = ConfigDict(extra="forbid")
89106

90-
pre: Alloc
107+
pre: Alloc = Field(default_factory=Alloc)
91108
post: Alloc = Field(default_factory=Alloc)
92109
tx: Transaction | None = None
93110
blocks: List[Block] | None = None
@@ -118,6 +135,14 @@ class BenchmarkTest(BaseTest):
118135
"blockchain_test_only": "Only generate a blockchain test fixture",
119136
}
120137

138+
def model_post_init(self, __context: Any, /) -> None:
139+
"""
140+
Model post-init to assert that the custom pre-allocation was
141+
provided and the default was not used.
142+
"""
143+
super().model_post_init(__context)
144+
assert "pre" in self.model_fields_set, "pre allocation was not provided"
145+
121146
@classmethod
122147
def pytest_parameter_name(cls) -> str:
123148
"""
@@ -178,9 +203,11 @@ def generate_blocks_from_code_generator(self, fork: Fork) -> List[Block]:
178203
if self.code_generator is None:
179204
raise Exception("Code generator is not set")
180205

181-
self.code_generator.deploy_contracts(self.pre, fork)
206+
self.code_generator.deploy_contracts(pre=self.pre, fork=fork)
182207
gas_limit = fork.transaction_gas_limit_cap() or self.gas_benchmark_value
183-
benchmark_tx = self.code_generator.generate_transaction(self.pre, gas_limit, fork)
208+
benchmark_tx = self.code_generator.generate_transaction(
209+
pre=self.pre, gas_benchmark_value=gas_limit
210+
)
184211

185212
execution_txs = self.split_transaction(benchmark_tx, gas_limit)
186213
execution_block = Block(txs=execution_txs)

tests/benchmark/test_worst_blocks.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,6 @@ def test_block_full_data(
252252
)
253253

254254
benchmark_test(
255-
pre=pre,
256-
post={},
257255
blocks=[Block(txs=txs)],
258256
expected_benchmark_gas_used=total_gas_used,
259257
)
@@ -361,8 +359,6 @@ def test_block_full_access_list_and_data(
361359
)
362360

363361
benchmark_test(
364-
pre=pre,
365-
post={},
366362
blocks=[Block(txs=txs)],
367363
expected_benchmark_gas_used=total_gas_used,
368364
)

tests/benchmark/test_worst_bytecode.py

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
Block,
1616
BlockchainTestFiller,
1717
Bytecode,
18-
Bytes,
1918
Environment,
2019
Hash,
2120
Transaction,
@@ -246,10 +245,8 @@ def test_worst_bytecode_single_opcode(
246245
)
247246
def test_worst_initcode_jumpdest_analysis(
248247
benchmark_test: BenchmarkTestFiller,
249-
pre: Alloc,
250248
fork: Fork,
251249
pattern: Bytecode,
252-
gas_benchmark_value: int,
253250
) -> None:
254251
"""
255252
Test the jumpdest analysis performance of the initcode.
@@ -298,15 +295,12 @@ def test_worst_initcode_jumpdest_analysis(
298295

299296
setup = code_prepare_initcode + Op.PUSH0
300297

301-
tx = JumpLoopGenerator(
302-
setup=setup, attack_block=attack_block, cleanup=Bytecode()
303-
).generate_transaction(pre, gas_benchmark_value, fork)
304-
tx.data = Bytes(tx_data)
305-
306298
benchmark_test(
307-
pre=pre,
308-
post={},
309-
tx=tx,
299+
code_generator=JumpLoopGenerator(
300+
setup=setup,
301+
attack_block=attack_block,
302+
tx_kwargs={"data": tx_data},
303+
),
310304
)
311305

312306

@@ -408,7 +402,7 @@ def test_worst_create(
408402
)
409403

410404
code = JumpLoopGenerator(setup=setup, attack_block=attack_block).generate_repeated_code(
411-
repeated_code=attack_block, setup=setup, cleanup=Bytecode(), fork=fork
405+
repeated_code=attack_block, setup=setup, fork=fork
412406
)
413407

414408
tx = Transaction(
@@ -417,11 +411,7 @@ def test_worst_create(
417411
sender=pre.fund_eoa(),
418412
)
419413

420-
benchmark_test(
421-
pre=pre,
422-
post={},
423-
tx=tx,
424-
)
414+
benchmark_test(tx=tx)
425415

426416

427417
@pytest.mark.parametrize(
@@ -483,7 +473,5 @@ def test_worst_creates_collisions(
483473
pre.deploy_contract(address=addr, code=Op.INVALID)
484474

485475
benchmark_test(
486-
pre=pre,
487-
post={},
488476
code_generator=JumpLoopGenerator(setup=setup, attack_block=attack_block),
489477
)

0 commit comments

Comments
 (0)