Skip to content

Commit ece0213

Browse files
committed
feat(forks): Add EIP-7623 logic to gas calculation
1 parent 3cd05e0 commit ece0213

File tree

3 files changed

+63
-4
lines changed

3 files changed

+63
-4
lines changed

src/ethereum_test_forks/base_fork.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ 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) -> int:
47+
def __call__(self, *, data: BytesConvertible, floor: bool = False) -> int:
4848
"""
4949
Returns the transaction gas cost of calldata given its contents.
5050
"""
@@ -63,9 +63,20 @@ def __call__(
6363
contract_creation: bool = False,
6464
access_list: List[AccessList] | None = None,
6565
authorization_list_or_count: Sized | int | None = None,
66+
return_cost_deducted_prior_execution: bool = False,
6667
) -> int:
6768
"""
6869
Returns the intrinsic gas cost of a transaction given its properties.
70+
71+
The `calldata` parameter is the data of the transaction.
72+
The `contract_creation` parameter is whether the transaction creates a contract.
73+
The `access_list` parameter is the list of access lists for the transaction.
74+
The `authorization_list_or_count` parameter is the list of authorizations or the count of
75+
authorizations for the transaction.
76+
If `return_cost_deducted_prior_execution` parameter if set to False, the returned value
77+
is equal to the minimum gas required for the transaction to be valid. If set to True, the
78+
returned value is equal to the cost that is deducted from the gas limit before the
79+
transaction starts execution.
6980
"""
7081
pass
7182

src/ethereum_test_forks/forks/forks.py

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ def gas_costs(cls, block_number: int = 0, timestamp: int = 0) -> GasCosts:
140140
G_MEMORY=3,
141141
G_TX_DATA_ZERO=4,
142142
G_TX_DATA_NON_ZERO=68,
143+
G_TX_DATA_STANDARD_TOKEN_COST=0,
144+
G_TX_DATA_FLOOR_TOKEN_COST=0,
143145
G_TRANSACTION=21_000,
144146
G_TRANSACTION_CREATE=32_000,
145147
G_LOG=375,
@@ -184,7 +186,7 @@ def calldata_gas_calculator(
184186
"""
185187
gas_costs = cls.gas_costs(block_number, timestamp)
186188

187-
def fn(*, data: BytesConvertible) -> int:
189+
def fn(*, data: BytesConvertible, floor: bool = False) -> int:
188190
cost = 0
189191
for b in Bytes(data):
190192
if b == 0:
@@ -211,6 +213,7 @@ def fn(
211213
contract_creation: bool = False,
212214
access_list: List[AccessList] | None = None,
213215
authorization_list_or_count: Sized | int | None = None,
216+
return_cost_deducted_prior_execution: bool = False,
214217
) -> int:
215218
assert access_list is None, f"Access list is not supported in {cls.name()}"
216219
assert (
@@ -633,6 +636,7 @@ def fn(
633636
contract_creation: bool = False,
634637
access_list: List[AccessList] | None = None,
635638
authorization_list_or_count: Sized | int | None = None,
639+
return_cost_deducted_prior_execution: bool = False,
636640
) -> int:
637641
intrinsic_cost: int = super_fn(
638642
calldata=calldata,
@@ -764,7 +768,7 @@ def valid_opcodes(
764768
@classmethod
765769
def gas_costs(cls, block_number: int = 0, timestamp: int = 0) -> GasCosts:
766770
"""
767-
Returns a dataclass with the defined gas costs constants for genesis.
771+
On Istanbul, the non-zero transaction data byte cost is reduced to 16 due to EIP-2028.
768772
"""
769773
return replace(
770774
super(Istanbul, cls).gas_costs(block_number, timestamp),
@@ -818,6 +822,7 @@ def fn(
818822
contract_creation: bool = False,
819823
access_list: List[AccessList] | None = None,
820824
authorization_list_or_count: Sized | int | None = None,
825+
return_cost_deducted_prior_execution: bool = False,
821826
) -> int:
822827
intrinsic_cost: int = super_fn(
823828
calldata=calldata,
@@ -1127,6 +1132,17 @@ def precompiles(cls, block_number: int = 0, timestamp: int = 0) -> List[Address]
11271132
block_number, timestamp
11281133
)
11291134

1135+
@classmethod
1136+
def gas_costs(cls, block_number: int = 0, timestamp: int = 0) -> GasCosts:
1137+
"""
1138+
On Prague, the standard token cost and the floor token costs are introduced due to EIP-7623
1139+
"""
1140+
return replace(
1141+
super(Prague, cls).gas_costs(block_number, timestamp),
1142+
G_TX_DATA_STANDARD_TOKEN_COST=4, # https://eips.ethereum.org/EIPS/eip-7623
1143+
G_TX_DATA_FLOOR_TOKEN_COST=10,
1144+
)
1145+
11301146
@classmethod
11311147
def system_contracts(cls, block_number: int = 0, timestamp: int = 0) -> List[Address]:
11321148
"""
@@ -1146,6 +1162,29 @@ def max_request_type(cls, block_number: int = 0, timestamp: int = 0) -> int:
11461162
"""
11471163
return 2
11481164

1165+
@classmethod
1166+
def calldata_gas_calculator(
1167+
cls, block_number: int = 0, timestamp: int = 0
1168+
) -> CalldataGasCalculator:
1169+
"""
1170+
Returns a callable that calculates the transaction gas cost for its calldata
1171+
depending on its contents.
1172+
"""
1173+
gas_costs = cls.gas_costs(block_number, timestamp)
1174+
1175+
def fn(*, data: BytesConvertible, floor: bool = False) -> int:
1176+
tokens = 0
1177+
for b in Bytes(data):
1178+
if b == 0:
1179+
tokens += 1
1180+
else:
1181+
tokens += 4
1182+
if floor:
1183+
return tokens * gas_costs.G_TX_DATA_FLOOR_TOKEN_COST
1184+
return tokens * gas_costs.G_TX_DATA_STANDARD_TOKEN_COST
1185+
1186+
return fn
1187+
11491188
@classmethod
11501189
def transaction_intrinsic_cost_calculator(
11511190
cls, block_number: int = 0, timestamp: int = 0
@@ -1164,17 +1203,24 @@ def fn(
11641203
contract_creation: bool = False,
11651204
access_list: List[AccessList] | None = None,
11661205
authorization_list_or_count: Sized | int | None = None,
1206+
return_cost_deducted_prior_execution: bool = False,
11671207
) -> int:
11681208
intrinsic_cost: int = super_fn(
11691209
calldata=calldata,
11701210
contract_creation=contract_creation,
11711211
access_list=access_list,
1212+
return_cost_deducted_prior_execution=False,
11721213
)
11731214
if authorization_list_or_count is not None:
11741215
if isinstance(authorization_list_or_count, Sized):
11751216
authorization_list_or_count = len(authorization_list_or_count)
11761217
intrinsic_cost += authorization_list_or_count * gas_costs.G_AUTHORIZATION
1177-
return intrinsic_cost
1218+
1219+
if return_cost_deducted_prior_execution:
1220+
return intrinsic_cost
1221+
1222+
floor_data_cost = cls.calldata_gas_calculator()(data=calldata, floor=True)
1223+
return max(intrinsic_cost, floor_data_cost + gas_costs.G_TRANSACTION)
11781224

11791225
return fn
11801226

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

0 commit comments

Comments
 (0)