Skip to content

Commit d8daa55

Browse files
committed
attempt 1
1 parent 6493b33 commit d8daa55

File tree

3 files changed

+270
-91
lines changed

3 files changed

+270
-91
lines changed

tests/prague/eip7623_increase_calldata_cost/conftest.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,30 @@ def tx_gas_delta() -> int:
231231

232232

233233
@pytest.fixture
234-
def tx_intrinsic_gas_cost(
234+
def tx_intrinsic_gas_cost_before_execution(
235+
fork: Fork,
236+
tx_data: Bytes,
237+
access_list: List[AccessList] | None,
238+
authorization_list: List[AuthorizationTuple] | None,
239+
contract_creating_tx: bool,
240+
) -> int:
241+
"""
242+
Return the intrinsic gas cost that is applied before the execution start.
243+
244+
This value never includes the floor data gas cost.
245+
"""
246+
intrinsic_gas_cost_calculator = fork.transaction_intrinsic_cost_calculator()
247+
return intrinsic_gas_cost_calculator(
248+
calldata=tx_data,
249+
contract_creation=contract_creating_tx,
250+
access_list=access_list,
251+
authorization_list_or_count=authorization_list,
252+
return_cost_deducted_prior_execution=True,
253+
)
254+
255+
256+
@pytest.fixture
257+
def tx_intrinsic_gas_cost_including_floor_data_cost(
235258
fork: Fork,
236259
tx_data: Bytes,
237260
access_list: List[AccessList] | None,
@@ -242,7 +265,9 @@ def tx_intrinsic_gas_cost(
242265
Transaction intrinsic gas cost.
243266
244267
The calculated value takes into account the normal intrinsic gas cost and the floor data gas
245-
cost.
268+
cost if it is greater than the intrinsic gas cost.
269+
270+
In other words, this is the value that is required for the transaction to be valid.
246271
"""
247272
intrinsic_gas_cost_calculator = fork.transaction_intrinsic_cost_calculator()
248273
return intrinsic_gas_cost_calculator(
@@ -265,15 +290,15 @@ def tx_floor_data_cost(
265290

266291
@pytest.fixture
267292
def tx_gas_limit(
268-
tx_intrinsic_gas_cost: int,
293+
tx_intrinsic_gas_cost_including_floor_data_cost: int,
269294
tx_gas_delta: int,
270295
) -> int:
271296
"""
272297
Gas limit for the transaction.
273298
274299
The gas delta is added to the intrinsic gas cost to generate different test scenarios.
275300
"""
276-
return tx_intrinsic_gas_cost + tx_gas_delta
301+
return tx_intrinsic_gas_cost_including_floor_data_cost + tx_gas_delta
277302

278303

279304
@pytest.fixture

tests/prague/eip7623_increase_calldata_cost/test_execution_gas.py

Lines changed: 0 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -45,93 +45,6 @@ def authorization_refund() -> bool:
4545
return True
4646

4747

48-
class TestGasRefunds:
49-
"""Test gas refunds with EIP-7623 active."""
50-
51-
@pytest.fixture
52-
def intrinsic_gas_data_floor_minimum_delta(self) -> int:
53-
"""
54-
In this test we reset a storage key to zero to induce a refund,
55-
but we need to make sure that the floor is higher than the gas
56-
used during execution in order for the refund to be applied to
57-
the floor.
58-
"""
59-
return 50_000
60-
61-
@pytest.fixture
62-
def to(
63-
self,
64-
pre: Alloc,
65-
) -> Address | None:
66-
"""Return a contract that when executed results in refunds due to storage clearing."""
67-
return pre.deploy_contract(Op.SSTORE(0, 0) + Op.STOP, storage={0: 1})
68-
69-
@pytest.fixture
70-
def refund(self, fork: Fork, ty: int) -> int:
71-
"""Return the refund gas of the transaction."""
72-
gas_costs = fork.gas_costs()
73-
refund = gas_costs.R_STORAGE_CLEAR
74-
if ty == 4:
75-
refund += gas_costs.R_AUTHORIZATION_EXISTING_AUTHORITY
76-
return refund
77-
78-
@pytest.mark.parametrize(
79-
"ty,protected,authorization_list",
80-
[
81-
pytest.param(0, False, None, id="type_0_unprotected"),
82-
pytest.param(0, True, None, id="type_0_protected"),
83-
pytest.param(1, True, None, id="type_1"),
84-
pytest.param(2, True, None, id="type_2"),
85-
pytest.param(
86-
3,
87-
True,
88-
None,
89-
id="type_3",
90-
),
91-
pytest.param(
92-
4,
93-
True,
94-
[Address(1)],
95-
id="type_4_with_authorization_refund",
96-
),
97-
],
98-
indirect=["authorization_list"],
99-
)
100-
@pytest.mark.parametrize(
101-
"tx_gas_delta",
102-
[
103-
# Test with exact gas and extra gas, to verify that the refund is correctly applied up
104-
# to the floor data cost.
105-
pytest.param(1, id="extra_gas"),
106-
pytest.param(0, id="exact_gas"),
107-
],
108-
)
109-
def test_gas_refunds_from_data_floor(
110-
self,
111-
state_test: StateTestFiller,
112-
pre: Alloc,
113-
tx: Transaction,
114-
tx_floor_data_cost: int,
115-
refund: int,
116-
) -> None:
117-
"""
118-
Test gas refunds deducted from the data floor.
119-
120-
I.e. the used gas by the intrinsic gas cost plus the execution cost is less than the data
121-
floor, hence data floor is used, and then the gas refunds are applied to the data floor.
122-
"""
123-
tx.expected_receipt = TransactionReceipt(gas_used=tx_floor_data_cost - refund)
124-
state_test(
125-
pre=pre,
126-
post={
127-
tx.to: {
128-
"storage": {0: 0}, # Verify storage was cleared
129-
}
130-
},
131-
tx=tx,
132-
)
133-
134-
13548
class TestGasConsumption:
13649
"""Test gas consumption with EIP-7623 active."""
13750

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
"""
2+
abstract: Test [EIP-7623: Increase calldata cost](https://eips.ethereum.org/EIPS/eip-7623)
3+
Test applied refunds for [EIP-7623: Increase calldata cost](https://eips.ethereum.org/EIPS/eip-7623).
4+
""" # noqa: E501
5+
6+
from enum import Enum, Flag, auto
7+
from typing import Dict
8+
9+
import pytest
10+
11+
from ethereum_test_forks import Fork, Prague
12+
from ethereum_test_tools import (
13+
Address,
14+
Alloc,
15+
Bytecode,
16+
StateTestFiller,
17+
Transaction,
18+
TransactionReceipt,
19+
)
20+
from ethereum_test_tools import Opcodes as Op
21+
22+
from .helpers import DataTestType
23+
from .spec import ref_spec_7623
24+
25+
REFERENCE_SPEC_GIT_PATH = ref_spec_7623.git_path
26+
REFERENCE_SPEC_VERSION = ref_spec_7623.version
27+
28+
ENABLE_FORK = Prague
29+
pytestmark = [pytest.mark.valid_from(str(ENABLE_FORK))]
30+
31+
32+
class RefundTestType(Enum):
33+
"""Refund test type."""
34+
35+
EXECUTION_GAS_MINUS_REFUND_GREATER_THAN_DATA_FLOOR = 0
36+
"""
37+
The execution gas minus the refund is greater than the data floor, hence the execution gas cost
38+
is charged.
39+
"""
40+
EXECUTION_GAS_MINUS_REFUND_LESS_THAN_DATA_FLOOR = 1
41+
"""
42+
The execution gas minus the refund is less than the data floor, hence the data floor cost is
43+
charged.
44+
"""
45+
46+
47+
class RefundType(Flag):
48+
"""Refund type."""
49+
50+
STORAGE_CLEAR = auto()
51+
"""The storage is cleared from a non-zero value."""
52+
53+
AUTHORIZATION_EXISTING_AUTHORITY = auto()
54+
"""The authorization list contains an authorization where the authority exists in the state."""
55+
56+
57+
@pytest.fixture
58+
def data_test_type() -> DataTestType:
59+
"""Return data test type."""
60+
return DataTestType.FLOOR_GAS_COST_GREATER_THAN_INTRINSIC_GAS
61+
62+
63+
@pytest.fixture
64+
def authorization_refund(refund_type: RefundType) -> int:
65+
"""Modify fixture from conftest to automatically read the refund_type information."""
66+
return RefundType.AUTHORIZATION_EXISTING_AUTHORITY in refund_type
67+
68+
69+
@pytest.fixture
70+
def ty(refund_type: RefundType) -> int:
71+
"""Modify fixture from conftest to automatically read the refund_type information."""
72+
if RefundType.AUTHORIZATION_EXISTING_AUTHORITY in refund_type:
73+
return 4
74+
return 2
75+
76+
77+
@pytest.fixture
78+
def max_refund(fork: Fork, refund_type: RefundType) -> int:
79+
"""Return the max refund gas of the transaction."""
80+
gas_costs = fork.gas_costs()
81+
max_refund = gas_costs.R_STORAGE_CLEAR if RefundType.STORAGE_CLEAR in refund_type else 0
82+
max_refund += (
83+
gas_costs.R_AUTHORIZATION_EXISTING_AUTHORITY
84+
if RefundType.AUTHORIZATION_EXISTING_AUTHORITY in refund_type
85+
else 0
86+
)
87+
return max_refund
88+
89+
90+
@pytest.fixture
91+
def prefix_code_gas(fork: Fork, refund_type: RefundType) -> int:
92+
"""Return the minimum execution gas cost due to the refund type."""
93+
if RefundType.STORAGE_CLEAR in refund_type:
94+
# Minimum code to generate a storage clear is Op.SSTORE(0, 0).
95+
gas_costs = fork.gas_costs()
96+
return gas_costs.G_COLD_SLOAD + gas_costs.G_STORAGE_RESET + (gas_costs.G_VERY_LOW * 2)
97+
return 0
98+
99+
100+
@pytest.fixture
101+
def prefix_code(refund_type: RefundType) -> Bytecode:
102+
"""Return the minimum execution gas cost due to the refund type."""
103+
if RefundType.STORAGE_CLEAR in refund_type:
104+
# Clear the storage to trigger a refund.
105+
return Op.SSTORE(0, 0)
106+
return Bytecode()
107+
108+
109+
@pytest.fixture
110+
def code_storage(refund_type: RefundType) -> Dict:
111+
"""Return the minimum execution gas cost due to the refund type."""
112+
if RefundType.STORAGE_CLEAR in refund_type:
113+
# Pre-set the storage to be cleared.
114+
return {0: 1}
115+
return {}
116+
117+
118+
@pytest.fixture
119+
def execution_gas_used(
120+
tx_intrinsic_gas_cost_before_execution: int,
121+
tx_floor_data_cost: int,
122+
max_refund: int,
123+
prefix_code_gas: int,
124+
refund_test_type: RefundTestType,
125+
) -> int:
126+
"""
127+
Return the amount of gas that needs to be consumed by the execution.
128+
129+
This gas amount is on top of the transaction intrinsic gas cost.
130+
131+
Since intrinsic_gas_data_floor_minimum_delta is zero for all test cases, if this value is zero
132+
it will result in the refund being applied to the execution gas cost and the resulting amount
133+
being always below the floor data cost.
134+
"""
135+
136+
def execution_gas_cost(execution_gas: int) -> int:
137+
total_gas_used = tx_intrinsic_gas_cost_before_execution + execution_gas
138+
return total_gas_used - min(max_refund, total_gas_used // 5)
139+
140+
execution_gas = prefix_code_gas
141+
142+
assert execution_gas_cost(execution_gas) < tx_floor_data_cost, "tx_floor_data_cost is too low"
143+
144+
# Dumb for-loop to find the execution gas cost that will result in the expected refund.
145+
while execution_gas_cost(execution_gas) < tx_floor_data_cost:
146+
execution_gas += 1
147+
if refund_test_type == RefundTestType.EXECUTION_GAS_MINUS_REFUND_GREATER_THAN_DATA_FLOOR:
148+
return execution_gas
149+
elif refund_test_type == RefundTestType.EXECUTION_GAS_MINUS_REFUND_LESS_THAN_DATA_FLOOR:
150+
return execution_gas - 1
151+
152+
raise ValueError("Invalid refund test type")
153+
154+
155+
@pytest.fixture
156+
def refund(
157+
tx_intrinsic_gas_cost_before_execution: int,
158+
execution_gas_used: int,
159+
max_refund: int,
160+
) -> int:
161+
"""Return the refund gas of the transaction."""
162+
total_gas_used = tx_intrinsic_gas_cost_before_execution + execution_gas_used
163+
return min(max_refund, total_gas_used // 5)
164+
165+
166+
@pytest.fixture
167+
def to(
168+
pre: Alloc,
169+
execution_gas_used: int,
170+
prefix_code: Bytecode,
171+
prefix_code_gas: int,
172+
code_storage: Dict,
173+
) -> Address | None:
174+
"""Return a contract that consumes the expected execution gas."""
175+
return pre.deploy_contract(
176+
prefix_code + (Op.JUMPDEST * (execution_gas_used - prefix_code_gas)) + Op.STOP,
177+
storage=code_storage,
178+
)
179+
180+
181+
@pytest.fixture
182+
def tx_gas_limit(
183+
tx_intrinsic_gas_cost_including_floor_data_cost: int,
184+
tx_intrinsic_gas_cost_before_execution: int,
185+
execution_gas_used: int,
186+
) -> int:
187+
"""
188+
Gas limit for the transaction.
189+
190+
The gas delta is added to the intrinsic gas cost to generate different test scenarios.
191+
"""
192+
tx_gas_limit = tx_intrinsic_gas_cost_before_execution + execution_gas_used
193+
assert tx_gas_limit >= tx_intrinsic_gas_cost_including_floor_data_cost
194+
return tx_gas_limit
195+
196+
197+
@pytest.mark.parametrize(
198+
"refund_test_type",
199+
[
200+
RefundTestType.EXECUTION_GAS_MINUS_REFUND_GREATER_THAN_DATA_FLOOR,
201+
RefundTestType.EXECUTION_GAS_MINUS_REFUND_LESS_THAN_DATA_FLOOR,
202+
],
203+
)
204+
@pytest.mark.parametrize(
205+
"refund_type",
206+
[
207+
RefundType.STORAGE_CLEAR,
208+
RefundType.STORAGE_CLEAR | RefundType.AUTHORIZATION_EXISTING_AUTHORITY,
209+
RefundType.AUTHORIZATION_EXISTING_AUTHORITY,
210+
],
211+
)
212+
def test_gas_refunds_from_data_floor(
213+
state_test: StateTestFiller,
214+
pre: Alloc,
215+
tx: Transaction,
216+
tx_floor_data_cost: int,
217+
tx_intrinsic_gas_cost_before_execution: int,
218+
execution_gas_cost: int,
219+
refund: int,
220+
refund_test_type: RefundTestType,
221+
) -> None:
222+
"""Test gas refunds deducted from the execution gas cost and not the data floor."""
223+
gas_used = tx_intrinsic_gas_cost_before_execution + execution_gas_cost - refund
224+
if refund_test_type == RefundTestType.EXECUTION_GAS_MINUS_REFUND_LESS_THAN_DATA_FLOOR:
225+
assert gas_used < tx_floor_data_cost
226+
elif refund_test_type == RefundTestType.EXECUTION_GAS_MINUS_REFUND_GREATER_THAN_DATA_FLOOR:
227+
assert gas_used >= tx_floor_data_cost
228+
else:
229+
raise ValueError("Invalid refund test type")
230+
if gas_used < tx_floor_data_cost:
231+
gas_used = tx_floor_data_cost
232+
tx.expected_receipt = TransactionReceipt(gas_used=gas_used)
233+
state_test(
234+
pre=pre,
235+
post={
236+
tx.to: {
237+
"storage": {0: 0}, # Verify storage was cleared
238+
}
239+
},
240+
tx=tx,
241+
)

0 commit comments

Comments
 (0)