Skip to content

Commit 6446ed4

Browse files
marioevzreedsadanceratopz
authored
feat(forks,tests): Add EIP-7623 (#1004)
* feat(forks): Add EIP-7623 logic to gas calculation * fix(tests): Broken tests due to gas cost change * feat(forks): Add `transaction_data_floor_cost_calculator` * new(tests): EIP-7623: Add transaction validity tests * review comments * new(tests): EIP-7623: Add gas consumption tests * refactor(tests): EIP-7623: minor refactor * Update src/ethereum_test_forks/base_fork.py Co-authored-by: Stuart Reed <[email protected]> * Update src/ethereum_test_forks/base_fork.py * Apply suggestions from code review Co-authored-by: danceratopz <[email protected]> * fix(forks): tox * docs: changelog --------- Co-authored-by: Stuart Reed <[email protected]> Co-authored-by: danceratopz <[email protected]>
1 parent b820167 commit 6446ed4

File tree

13 files changed

+1162
-10
lines changed

13 files changed

+1162
-10
lines changed

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Test fixtures for use by clients are available for each release on the [Github r
1818
-[EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) many delegations test ([#923](https://github.com/ethereum/execution-spec-tests/pull/923))
1919
-[EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) set code of non-empty-storage account test ([#948](https://github.com/ethereum/execution-spec-tests/pull/948))
2020
-[EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) Remove delegation behavior of EXTCODE* ([#984](https://github.com/ethereum/execution-spec-tests/pull/984))
21+
-[EIP-7623](https://eips.ethereum.org/EIPS/eip-7623) Increase calldata cost ([#1004](https://github.com/ethereum/execution-spec-tests/pull/1004))
2122

2223
### 🛠️ Framework
2324

src/ethereum_test_forks/base_fork.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,18 @@ class CalldataGasCalculator(Protocol):
4444
A protocol to calculate the transaction gas cost of calldata for a given fork.
4545
"""
4646

47+
def __call__(self, *, data: BytesConvertible, floor: bool = False) -> int:
48+
"""
49+
Returns the transaction gas cost of calldata given its contents.
50+
"""
51+
pass
52+
53+
54+
class TransactionDataFloorCostCalculator(Protocol):
55+
"""
56+
A protocol to calculate the transaction floor cost due to its calldata for a given fork.
57+
"""
58+
4759
def __call__(self, *, data: BytesConvertible) -> int:
4860
"""
4961
Returns the transaction gas cost of calldata given its contents.
@@ -63,9 +75,24 @@ def __call__(
6375
contract_creation: bool = False,
6476
access_list: List[AccessList] | None = None,
6577
authorization_list_or_count: Sized | int | None = None,
78+
return_cost_deducted_prior_execution: bool = False,
6679
) -> int:
6780
"""
6881
Returns the intrinsic gas cost of a transaction given its properties.
82+
83+
Args:
84+
calldata: The data of the transaction.
85+
contract_creation: Whether the transaction creates a contract.
86+
access_list: The list of access lists for the transaction.
87+
authorization_list_or_count: The list of authorizations or the count of authorizations
88+
for the transaction.
89+
return_cost_deducted_prior_execution: If set to False, the returned value is equal to
90+
the minimum gas required for the transaction to be valid. If set to True, the
91+
returned value is equal to the cost that is deducted from the gas limit before
92+
the transaction starts execution.
93+
94+
Returns:
95+
Gas cost of a transaction
6996
"""
7097
pass
7198

@@ -236,6 +263,16 @@ def calldata_gas_calculator(
236263
"""
237264
pass
238265

266+
@classmethod
267+
@abstractmethod
268+
def transaction_data_floor_cost_calculator(
269+
cls, block_number: int = 0, timestamp: int = 0
270+
) -> TransactionDataFloorCostCalculator:
271+
"""
272+
Returns a callable that calculates the transaction floor cost due to its calldata.
273+
"""
274+
pass
275+
239276
@classmethod
240277
@abstractmethod
241278
def transaction_intrinsic_cost_calculator(

src/ethereum_test_forks/forks/forks.py

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
BaseFork,
1919
CalldataGasCalculator,
2020
MemoryExpansionGasCalculator,
21+
TransactionDataFloorCostCalculator,
2122
TransactionIntrinsicCostCalculator,
2223
)
2324
from ..gas_costs import GasCosts
@@ -140,6 +141,8 @@ def gas_costs(cls, block_number: int = 0, timestamp: int = 0) -> GasCosts:
140141
G_MEMORY=3,
141142
G_TX_DATA_ZERO=4,
142143
G_TX_DATA_NON_ZERO=68,
144+
G_TX_DATA_STANDARD_TOKEN_COST=0,
145+
G_TX_DATA_FLOOR_TOKEN_COST=0,
143146
G_TRANSACTION=21_000,
144147
G_TRANSACTION_CREATE=32_000,
145148
G_LOG=375,
@@ -184,7 +187,7 @@ def calldata_gas_calculator(
184187
"""
185188
gas_costs = cls.gas_costs(block_number, timestamp)
186189

187-
def fn(*, data: BytesConvertible) -> int:
190+
def fn(*, data: BytesConvertible, floor: bool = False) -> int:
188191
cost = 0
189192
for b in Bytes(data):
190193
if b == 0:
@@ -195,6 +198,19 @@ def fn(*, data: BytesConvertible) -> int:
195198

196199
return fn
197200

201+
@classmethod
202+
def transaction_data_floor_cost_calculator(
203+
cls, block_number: int = 0, timestamp: int = 0
204+
) -> TransactionDataFloorCostCalculator:
205+
"""
206+
At frontier, the transaction data floor cost is a constant zero.
207+
"""
208+
209+
def fn(*, data: BytesConvertible) -> int:
210+
return 0
211+
212+
return fn
213+
198214
@classmethod
199215
def transaction_intrinsic_cost_calculator(
200216
cls, block_number: int = 0, timestamp: int = 0
@@ -211,6 +227,7 @@ def fn(
211227
contract_creation: bool = False,
212228
access_list: List[AccessList] | None = None,
213229
authorization_list_or_count: Sized | int | None = None,
230+
return_cost_deducted_prior_execution: bool = False,
214231
) -> int:
215232
assert access_list is None, f"Access list is not supported in {cls.name()}"
216233
assert (
@@ -633,6 +650,7 @@ def fn(
633650
contract_creation: bool = False,
634651
access_list: List[AccessList] | None = None,
635652
authorization_list_or_count: Sized | int | None = None,
653+
return_cost_deducted_prior_execution: bool = False,
636654
) -> int:
637655
intrinsic_cost: int = super_fn(
638656
calldata=calldata,
@@ -764,7 +782,7 @@ def valid_opcodes(
764782
@classmethod
765783
def gas_costs(cls, block_number: int = 0, timestamp: int = 0) -> GasCosts:
766784
"""
767-
Returns a dataclass with the defined gas costs constants for genesis.
785+
On Istanbul, the non-zero transaction data byte cost is reduced to 16 due to EIP-2028.
768786
"""
769787
return replace(
770788
super(Istanbul, cls).gas_costs(block_number, timestamp),
@@ -818,6 +836,7 @@ def fn(
818836
contract_creation: bool = False,
819837
access_list: List[AccessList] | None = None,
820838
authorization_list_or_count: Sized | int | None = None,
839+
return_cost_deducted_prior_execution: bool = False,
821840
) -> int:
822841
intrinsic_cost: int = super_fn(
823842
calldata=calldata,
@@ -1127,6 +1146,17 @@ def precompiles(cls, block_number: int = 0, timestamp: int = 0) -> List[Address]
11271146
block_number, timestamp
11281147
)
11291148

1149+
@classmethod
1150+
def gas_costs(cls, block_number: int = 0, timestamp: int = 0) -> GasCosts:
1151+
"""
1152+
On Prague, the standard token cost and the floor token costs are introduced due to EIP-7623
1153+
"""
1154+
return replace(
1155+
super(Prague, cls).gas_costs(block_number, timestamp),
1156+
G_TX_DATA_STANDARD_TOKEN_COST=4, # https://eips.ethereum.org/EIPS/eip-7623
1157+
G_TX_DATA_FLOOR_TOKEN_COST=10,
1158+
)
1159+
11301160
@classmethod
11311161
def system_contracts(cls, block_number: int = 0, timestamp: int = 0) -> List[Address]:
11321162
"""
@@ -1146,6 +1176,44 @@ def max_request_type(cls, block_number: int = 0, timestamp: int = 0) -> int:
11461176
"""
11471177
return 2
11481178

1179+
@classmethod
1180+
def calldata_gas_calculator(
1181+
cls, block_number: int = 0, timestamp: int = 0
1182+
) -> CalldataGasCalculator:
1183+
"""
1184+
Returns a callable that calculates the transaction gas cost for its calldata
1185+
depending on its contents.
1186+
"""
1187+
gas_costs = cls.gas_costs(block_number, timestamp)
1188+
1189+
def fn(*, data: BytesConvertible, floor: bool = False) -> int:
1190+
tokens = 0
1191+
for b in Bytes(data):
1192+
if b == 0:
1193+
tokens += 1
1194+
else:
1195+
tokens += 4
1196+
if floor:
1197+
return tokens * gas_costs.G_TX_DATA_FLOOR_TOKEN_COST
1198+
return tokens * gas_costs.G_TX_DATA_STANDARD_TOKEN_COST
1199+
1200+
return fn
1201+
1202+
@classmethod
1203+
def transaction_data_floor_cost_calculator(
1204+
cls, block_number: int = 0, timestamp: int = 0
1205+
) -> TransactionDataFloorCostCalculator:
1206+
"""
1207+
Starting in Prague, due to EIP-7623, the transaction data floor cost is introduced.
1208+
"""
1209+
calldata_gas_calculator = cls.calldata_gas_calculator(block_number, timestamp)
1210+
gas_costs = cls.gas_costs(block_number, timestamp)
1211+
1212+
def fn(*, data: BytesConvertible) -> int:
1213+
return calldata_gas_calculator(data=data, floor=True) + gas_costs.G_TRANSACTION
1214+
1215+
return fn
1216+
11491217
@classmethod
11501218
def transaction_intrinsic_cost_calculator(
11511219
cls, block_number: int = 0, timestamp: int = 0
@@ -1157,24 +1225,34 @@ def transaction_intrinsic_cost_calculator(
11571225
block_number, timestamp
11581226
)
11591227
gas_costs = cls.gas_costs(block_number, timestamp)
1228+
transaction_data_floor_cost_calculator = cls.transaction_data_floor_cost_calculator(
1229+
block_number, timestamp
1230+
)
11601231

11611232
def fn(
11621233
*,
11631234
calldata: BytesConvertible = b"",
11641235
contract_creation: bool = False,
11651236
access_list: List[AccessList] | None = None,
11661237
authorization_list_or_count: Sized | int | None = None,
1238+
return_cost_deducted_prior_execution: bool = False,
11671239
) -> int:
11681240
intrinsic_cost: int = super_fn(
11691241
calldata=calldata,
11701242
contract_creation=contract_creation,
11711243
access_list=access_list,
1244+
return_cost_deducted_prior_execution=False,
11721245
)
11731246
if authorization_list_or_count is not None:
11741247
if isinstance(authorization_list_or_count, Sized):
11751248
authorization_list_or_count = len(authorization_list_or_count)
11761249
intrinsic_cost += authorization_list_or_count * gas_costs.G_AUTHORIZATION
1177-
return intrinsic_cost
1250+
1251+
if return_cost_deducted_prior_execution:
1252+
return intrinsic_cost
1253+
1254+
transaction_floor_data_cost = transaction_data_floor_cost_calculator(data=calldata)
1255+
return max(intrinsic_cost, transaction_floor_data_cost)
11781256

11791257
return fn
11801258

src/ethereum_test_forks/gas_costs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ class GasCosts:
4545

4646
G_TX_DATA_ZERO: int
4747
G_TX_DATA_NON_ZERO: int
48+
G_TX_DATA_STANDARD_TOKEN_COST: int
49+
G_TX_DATA_FLOOR_TOKEN_COST: int
4850

4951
G_TRANSACTION: int
5052
G_TRANSACTION_CREATE: int

tests/cancun/eip4844_blobs/test_point_evaluation_precompile.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
from ethereum_test_forks import Fork
3939
from ethereum_test_tools import (
4040
EOA,
41+
AccessList,
4142
Account,
4243
Address,
4344
Alloc,
@@ -559,9 +560,15 @@ def test_tx_entry_point(
559560
start_balance = 10**18
560561
sender = pre.fund_eoa(amount=start_balance)
561562

563+
# Starting from EIP-7623, we need to use an access list to raise the intrinsic gas cost to be
564+
# above the floor data cost.
565+
access_list = [AccessList(address=Address(i), storage_keys=[]) for i in range(1, 10)]
566+
562567
# Gas is appended the intrinsic gas cost of the transaction
563568
tx_intrinsic_gas_cost_calculator = fork.transaction_intrinsic_cost_calculator()
564-
intrinsic_gas_cost = tx_intrinsic_gas_cost_calculator(calldata=precompile_input)
569+
intrinsic_gas_cost = tx_intrinsic_gas_cost_calculator(
570+
calldata=precompile_input, access_list=access_list
571+
)
565572

566573
# Consumed gas will only be the precompile gas if the proof is correct and
567574
# the call gas is sufficient.
@@ -570,13 +577,17 @@ def test_tx_entry_point(
570577
Spec.POINT_EVALUATION_PRECOMPILE_GAS
571578
if call_gas >= Spec.POINT_EVALUATION_PRECOMPILE_GAS and proof_correct
572579
else call_gas
573-
) + intrinsic_gas_cost
574-
580+
) + tx_intrinsic_gas_cost_calculator(
581+
calldata=precompile_input,
582+
access_list=access_list,
583+
return_cost_deducted_prior_execution=True,
584+
)
575585
fee_per_gas = 7
576586

577587
tx = Transaction(
578588
sender=sender,
579589
data=precompile_input,
590+
access_list=access_list,
580591
to=Address(Spec.POINT_EVALUATION_PRECOMPILE_ADDRESS),
581592
gas_limit=call_gas + intrinsic_gas_cost,
582593
gas_price=fee_per_gas,

tests/cancun/eip5656_mcopy/test_mcopy_memory_expansion.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
66
""" # noqa: E501
77
import itertools
8-
from typing import Mapping
8+
from typing import List, Mapping
99

1010
import pytest
1111

1212
from ethereum_test_forks import Fork
13-
from ethereum_test_tools import Account, Address, Alloc, Bytecode, Environment
13+
from ethereum_test_tools import AccessList, Account, Address, Alloc, Bytecode, Environment
1414
from ethereum_test_tools import Opcodes as Op
1515
from ethereum_test_tools import StateTestFiller, Transaction
1616

@@ -51,16 +51,27 @@ def callee_bytecode(dest: int, src: int, length: int) -> Bytecode:
5151
return bytecode
5252

5353

54+
@pytest.fixture
55+
def tx_access_list() -> List[AccessList]:
56+
"""
57+
Access list for the transaction.
58+
"""
59+
return [AccessList(address=Address(i), storage_keys=[]) for i in range(1, 10)]
60+
61+
5462
@pytest.fixture
5563
def call_exact_cost(
5664
fork: Fork,
5765
initial_memory: bytes,
5866
dest: int,
5967
length: int,
68+
tx_access_list: List[AccessList],
6069
) -> int:
6170
"""
6271
Returns the exact cost of the subcall, based on the initial memory and the length of the copy.
6372
"""
73+
# Starting from EIP-7623, we need to use an access list to raise the intrinsic gas cost to be
74+
# above the floor data cost.
6475
cost_memory_bytes = fork.memory_expansion_gas_calculator()
6576
gas_costs = fork.gas_costs()
6677
tx_intrinsic_gas_cost_calculator = fork.transaction_intrinsic_cost_calculator()
@@ -81,7 +92,7 @@ def call_exact_cost(
8192

8293
sstore_cost = 22100
8394
return (
84-
tx_intrinsic_gas_cost_calculator(calldata=initial_memory)
95+
tx_intrinsic_gas_cost_calculator(calldata=initial_memory, access_list=tx_access_list)
8596
+ mcopy_cost
8697
+ calldatacopy_cost
8798
+ pushes_cost
@@ -133,10 +144,12 @@ def tx( # noqa: D103
133144
initial_memory: bytes,
134145
tx_max_fee_per_gas: int,
135146
tx_gas_limit: int,
147+
tx_access_list: List[AccessList],
136148
) -> Transaction:
137149
return Transaction(
138150
sender=sender,
139151
to=caller_address,
152+
access_list=tx_access_list,
140153
data=initial_memory,
141154
gas_limit=tx_gas_limit,
142155
max_fee_per_gas=tx_max_fee_per_gas,
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
"""
2+
abstract: Test [EIP-7623: Increase calldata cost](https://eips.ethereum.org/EIPS/eip-7623)
3+
Tests for [EIP-7623: Increase calldata cost](https://eips.ethereum.org/EIPS/eip-7623).
4+
"""

0 commit comments

Comments
 (0)