Skip to content

Commit 9e9bf90

Browse files
LouisTsai-Csiemarioevzjochem-brouwerspencer-tb
authored
refactor(test-benchmark): align test suite with glam-devnet-7 spec (#3187)
Co-authored-by: marioevz <marioevz@gmail.com> Co-authored-by: Jochem Brouwer <jochembrouwer96@gmail.com> Co-authored-by: spencer-tb <spencer.tb@ethereum.org>
1 parent 2119b38 commit 9e9bf90

37 files changed

Lines changed: 2277 additions & 1325 deletions

.github/configs/evm.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
benchmark:
22
impl: geth
33
repo: ethereum/go-ethereum
4-
ref: master
4+
ref: glamsterdam-devnet-7
55
evm-bin: evm
66
xdist: auto
77
eels:

.github/configs/feature.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ tests:
1414

1515
benchmark:
1616
evm-type: benchmark
17-
fill-params: --fork=Osaka --generate-all-formats --gas-benchmark-values 1,5,10,30,60,100,150 ./tests/benchmark/compute --maxprocesses=30 --dist=worksteal
17+
fill-params: --fork=Amsterdam --generate-all-formats --gas-benchmark-values 1,5,10,30,60,100,150 ./tests/benchmark/compute --maxprocesses=30 --dist=worksteal
1818
feature_only: true
1919

2020
benchmark_fast:
2121
evm-type: benchmark
22-
fill-params: --fork=Osaka --generate-all-formats --gas-benchmark-values 100 ./tests/benchmark/compute
22+
fill-params: --fork=Amsterdam --generate-all-formats --gas-benchmark-values 100 ./tests/benchmark/compute
2323
feature_only: true
2424

2525
# Shared entry for all `<feat>-devnet` releases; matched by `-devnet` suffix.

Justfile

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ bench-gas *args:
261261
--generate-pre-alloc-groups \
262262
--evm-bin="{{ evm_bin }}" \
263263
--gas-benchmark-values 1 \
264-
--fork Osaka \
264+
--fork Amsterdam \
265265
-m "not slow" \
266266
-n auto --maxprocesses 10 --dist=loadgroup \
267267
--output="{{ output_dir }}/bench-gas/pre-alloc" \
@@ -274,7 +274,7 @@ bench-gas *args:
274274
uv run fill \
275275
--evm-bin="{{ evm_bin }}" \
276276
--gas-benchmark-values 1 \
277-
--fork Osaka \
277+
--fork Amsterdam \
278278
-m "blockchain_test and (not derived_test) and (not slow)" \
279279
-n auto --maxprocesses 10 --dist=loadgroup \
280280
--durations=20 \
@@ -288,7 +288,7 @@ bench-gas *args:
288288
@rm -rf tests/json_loader/bench_gas_fixtures
289289
ln -sfn "{{ output_dir }}/bench-gas/fixtures" tests/json_loader/bench_gas_fixtures
290290
cd tests/json_loader && uv run --python pypy3.11 --no-dev --group test pytest \
291-
--fork Osaka \
291+
--fork Amsterdam \
292292
--allow-post-state-hash \
293293
-n auto --maxprocesses 10 --dist=loadfile \
294294
--durations=20 \
@@ -302,8 +302,8 @@ bench-opcode *args:
302302
uv run fill \
303303
--evm-bin="{{ evm_bin }}" \
304304
--fixed-opcode-count 1 \
305-
--fork Osaka \
306-
-m repricing \
305+
--fork Amsterdam \
306+
-m "repricing and not slow" \
307307
-n auto --maxprocesses 10 --dist=loadgroup \
308308
-k "not test_alt_bn128 and not test_bls12_381 and not test_modexp and not uncachable" \
309309
--output="{{ output_dir }}/bench-opcode/fixtures" \
@@ -321,10 +321,10 @@ bench-opcode-config *args:
321321
uv run fill \
322322
--evm-bin="{{ evm_bin }}" \
323323
--fixed-opcode-count \
324-
--fork Osaka \
325-
-m repricing \
324+
--fork Amsterdam \
325+
-m "repricing and not slow" \
326326
-n auto --maxprocesses 10 --dist=loadgroup \
327-
-k "not test_alt_bn128 and not test_bls12_381 and not test_modexp and not test_point_evaluation_uncachable" \
327+
-k "not test_alt_bn128 and not test_bls12_381 and not test_modexp and not uncachable" \
328328
--output="{{ output_dir }}/bench-opcode-config/fixtures" \
329329
--basetemp="{{ output_dir }}/bench-opcode-config/tmp" \
330330
--log-to "{{ output_dir }}/bench-opcode-config/logs" \

packages/testing/src/execution_testing/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
SequentialAddressLayout,
111111
Switch,
112112
TransactionWithCost,
113+
TxOutcome,
113114
While,
114115
WhileGas,
115116
extend_with_defaults,
@@ -226,6 +227,7 @@
226227
"TransactionTestFiller",
227228
"TransactionType",
228229
"TransactionWithCost",
230+
"TxOutcome",
229231
"TransitionFork",
230232
"While",
231233
"WhileGas",

packages/testing/src/execution_testing/benchmark/benchmark_code_generator.py

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,21 +100,38 @@ def deploy_contracts(self, *, pre: Alloc, fork: Fork) -> Address:
100100

101101
# Create caller contract that repeatedly calls the target contract
102102
# attack = POP(
103-
# STATICCALL(GAS, target_contract_address, 0, 0, 0, 0)
103+
# (STATIC)CALL(GAS, target_contract_address, ...)
104104
# )
105105
#
106106
# setup + JUMPDEST + attack + attack + ... + attack +
107107
# JUMP(setup_length)
108-
code_sequence = Op.POP(
109-
Op.STATICCALL(
110-
Op.GAS,
111-
self._target_contract_address,
112-
Op.PUSH0,
113-
Op.CALLDATASIZE,
114-
Op.PUSH0,
115-
Op.PUSH0,
108+
#
109+
# The target must be entered via CALL when it contains
110+
# state-changing opcodes: a STATICCALL'd frame faults on the
111+
# first one and the target executes nothing.
112+
# A state-changing target must be entered via CALL, not STATICCALL.
113+
# CALL takes a value argument STATICCALL lacks; push the zero value
114+
# with PUSH0, one gas cheaper than the default PUSH1 0x00.
115+
if self.uses_state_changing_opcode():
116+
wrapper_call = Op.CALL(
117+
gas=Op.GAS,
118+
address=self._target_contract_address,
119+
value=Op.PUSH0,
120+
args_offset=Op.PUSH0,
121+
args_size=Op.CALLDATASIZE,
122+
ret_offset=Op.PUSH0,
123+
ret_size=Op.PUSH0,
116124
)
117-
)
125+
else:
126+
wrapper_call = Op.STATICCALL(
127+
gas=Op.GAS,
128+
address=self._target_contract_address,
129+
args_offset=Op.PUSH0,
130+
args_size=Op.CALLDATASIZE,
131+
ret_offset=Op.PUSH0,
132+
ret_size=Op.PUSH0,
133+
)
134+
code_sequence = Op.POP(wrapper_call)
118135

119136
caller_code = self.generate_repeated_code(
120137
setup=Op.CALLDATACOPY(Op.PUSH0, Op.PUSH0, Op.CALLDATASIZE),

packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
)
3030
from execution_testing.forks import Fork, TransitionFork
3131
from execution_testing.logging import get_logger
32+
from execution_testing.recipient_type import RecipientType
3233
from execution_testing.rpc import EthRPC
3334
from execution_testing.rpc.rpc_types import TransactionByHashResponse
3435
from execution_testing.test_types import (
@@ -287,8 +288,11 @@ def _compute_deploy_gas_limit(
287288
else:
288289
regular_gas = buffered_regular_gas
289290

290-
# State portion, from the block reservoir.
291+
# State portion, from the block reservoir. The created account's
292+
# NEW_ACCOUNT is charged at the top frame for create transactions
293+
# and by CREATE2 at access for proxy deploys — same amount.
291294
state_gas = fork.code_deposit_state_gas(code_size=deploy_code_size)
295+
state_gas += fork.transaction_top_frame_state_gas(contract_creation=True)
292296
state_gas += storage_slots * sstore_state_gas
293297

294298
deploy_gas_limit = regular_gas + state_gas
@@ -630,6 +634,35 @@ def _fund_eoa(
630634
)
631635
intrinsic_calc = fork.transaction_intrinsic_cost_calculator()
632636

637+
worst_case_auth = [
638+
AuthorizationTuple(
639+
address=Address(0),
640+
v=0,
641+
r=0,
642+
s=0,
643+
creates_account=True,
644+
writes_delegation=True,
645+
first_write=True,
646+
)
647+
]
648+
auth_fund_gas_limit = (
649+
intrinsic_calc(
650+
authorization_list_or_count=1,
651+
sends_value=True,
652+
recipient_type=RecipientType.EMPTY_ACCOUNT,
653+
)
654+
+ fork.transaction_top_frame_gas_calculator()(
655+
sends_value=True,
656+
recipient_type=RecipientType.EMPTY_ACCOUNT,
657+
authorizations=worst_case_auth,
658+
)
659+
+ fork.transaction_top_frame_state_gas(
660+
sends_value=True,
661+
recipient_type=RecipientType.EMPTY_ACCOUNT,
662+
authorizations=worst_case_auth,
663+
)
664+
)
665+
633666
if storage is not None:
634667
if not isinstance(storage, Storage):
635668
storage = Storage.model_validate(storage)
@@ -703,7 +736,7 @@ def _fund_eoa(
703736
signer=eoa,
704737
),
705738
],
706-
gas_limit=(intrinsic_calc(authorization_list_or_count=1)),
739+
gas_limit=auth_fund_gas_limit,
707740
)
708741
eoa.nonce = Number(eoa.nonce + 1)
709742
else:
@@ -721,7 +754,7 @@ def _fund_eoa(
721754
signer=eoa,
722755
),
723756
],
724-
gas_limit=intrinsic_calc(authorization_list_or_count=1),
757+
gas_limit=auth_fund_gas_limit,
725758
)
726759
eoa.nonce = Number(eoa.nonce + 1)
727760

packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1754,6 +1754,9 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
17541754
# If operation mode is benchmarking, check the gas used.
17551755
self.validate_benchmark_gas(
17561756
benchmark_gas_used=fill_result.benchmark_gas_used,
1757+
benchmark_block_gas_used=(
1758+
fill_result.benchmark_block_gas_used
1759+
),
17571760
gas_benchmark_value=gas_benchmark_value,
17581761
)
17591762

packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ def opcode_state_map(
150150
Opcodes.SELFDESTRUCT: (
151151
lambda op: cls._calculate_selfdestruct_state_gas(op, gas_costs)
152152
),
153+
Opcodes.CALL: lambda op: cls._calculate_call_state_gas(
154+
op, gas_costs
155+
),
153156
}
154157

155158
@classmethod
@@ -400,3 +403,39 @@ def _calculate_selfdestruct_gas(
400403
if opcode.metadata["account_new"]:
401404
gas_cost -= gas_costs.NEW_ACCOUNT
402405
return gas_cost
406+
407+
@classmethod
408+
def _calculate_call_state_gas(
409+
cls, opcode: OpcodeBase, gas_costs: GasCosts
410+
) -> int:
411+
"""
412+
Calculate the CALL state gas cost: `NEW_ACCOUNT` when a value
413+
transfer funds a new account. Before EIP-8037 this was folded
414+
into the regular CALL cost (EIP-161); under EIP-8037 it is
415+
exposed here as state gas, mirroring
416+
`_calculate_selfdestruct_state_gas`.
417+
"""
418+
metadata = opcode.metadata
419+
if "value_transfer" in metadata and metadata["value_transfer"]:
420+
if metadata["account_new"]:
421+
return gas_costs.NEW_ACCOUNT
422+
return 0
423+
424+
@classmethod
425+
def _calculate_call_gas(
426+
cls, opcode: OpcodeBase, gas_costs: GasCosts
427+
) -> int:
428+
"""
429+
Calculate the regular CALL gas cost. The EIP-161 base
430+
calculation folds `NEW_ACCOUNT` into the regular cost when a
431+
value transfer funds a new account; EIP-8037 moves that charge
432+
to the state-gas dimension (see `_calculate_call_state_gas`),
433+
so this subtracts the `NEW_ACCOUNT` term back out of the
434+
inherited regular cost.
435+
"""
436+
gas_cost = super()._calculate_call_gas(opcode, gas_costs)
437+
metadata = opcode.metadata
438+
if "value_transfer" in metadata and metadata["value_transfer"]:
439+
if metadata["account_new"]:
440+
gas_cost -= gas_costs.NEW_ACCOUNT
441+
return gas_cost

packages/testing/src/execution_testing/specs/base.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ class FillResult(BaseModel):
9494
fixture: BaseFixture
9595
gas_optimization: int | None
9696
benchmark_gas_used: int | None = None
97+
benchmark_block_gas_used: int | None = None
9798
benchmark_opcode_count: OpcodeCount | None = None
9899
post_verifications: PostVerifications | None = None
99100
metadata: Dict[str, Any] = Field(default_factory=dict)
@@ -259,12 +260,25 @@ def get_genesis_environment(self) -> Environment:
259260
)
260261

261262
def validate_benchmark_gas(
262-
self, *, benchmark_gas_used: int | None, gas_benchmark_value: int
263+
self,
264+
*,
265+
benchmark_gas_used: int | None,
266+
gas_benchmark_value: int,
267+
benchmark_block_gas_used: int | None = None,
263268
) -> None:
264269
"""
265270
Validates the total consumed gas of the last block in the test matches
266271
the expectation of the benchmark test.
267272
273+
``benchmark_gas_used`` is the combined gas across all dimensions (the
274+
receipt ``cumulativeGasUsed``) and is checked against
275+
``expected_benchmark_gas_used``. ``benchmark_block_gas_used`` is the
276+
block-header gas, i.e. the maximum across the independent gas
277+
dimensions (EIP-8037); it is what must stay within the block gas
278+
limit, because the combined value can legitimately exceed it. When it
279+
is not available (e.g. execute mode), the combined value is used for
280+
the ceiling check instead.
281+
268282
Requires the following fields to be set:
269283
- expected_benchmark_gas_used
270284
- operation_mode
@@ -287,9 +301,17 @@ def validate_benchmark_gas(
287301
f"({expected_benchmark_gas_used}), "
288302
f"difference: {diff}"
289303
)
290-
# Gas used should never exceed the maximum benchmark gas allowed.
291-
assert benchmark_gas_used <= gas_benchmark_value, (
292-
f"benchmark_gas_used ({benchmark_gas_used}) exceeds maximum "
304+
# No single gas dimension may exceed the block gas limit. The
305+
# block-header gas is the max across dimensions; the combined
306+
# regular+state gas may exceed the target under EIP-8037, so the
307+
# ceiling is checked against the header value when available.
308+
block_gas_used = (
309+
benchmark_block_gas_used
310+
if benchmark_block_gas_used is not None
311+
else benchmark_gas_used
312+
)
313+
assert block_gas_used <= gas_benchmark_value, (
314+
f"benchmark block gas used ({block_gas_used}) exceeds maximum "
293315
"benchmark gas allowed for this configuration: "
294316
f"{gas_benchmark_value}"
295317
)

0 commit comments

Comments
 (0)