Skip to content

Commit 46d4490

Browse files
marioevzspencer-tb
andauthored
feat(tests): EIP-7825: Transaction Gas Limit Cap (#1711)
* feat(tests): add initial eip-7825 test cases. * chore: add exceptions mapper, clean up, ci fix. * chore(docs): changelog. * chore: spellcheck. * chore: fix coverage ci. --------- Co-authored-by: spencer-tb <[email protected]>
1 parent d7055d5 commit 46d4490

File tree

14 files changed

+241
-1
lines changed

14 files changed

+241
-1
lines changed

.github/workflows/coverage.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55
paths:
66
- "tests/**" # This triggers the workflow for any changes in the tests folder
77
- "!tests/prague/**" # exclude changes in 'tests/prague'
8+
- "!tests/osaka/**" # exclude changes in 'tests/osaka'
89
- "!tests/unscheduled/**" # exclude changes in 'tests/unscheduled'
910

1011
jobs:

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ Users can select any of the artifacts depending on their testing needs for their
6363
- ✨ EIP-7594: Sanity test cases to send blob transactions and verify `engine_getBlobsVX` using the `execute` command ([#1644](https://github.com/ethereum/execution-spec-tests/pull/1644)).
6464
- 🔀 Refactored EIP-145 static tests into python ([#1683](https://github.com/ethereum/execution-spec-tests/pull/1683)).
6565
- ✨ EIP-7823, EIP-7883: Add test cases for ModExp precompile gas-cost updates and input limits on Osaka ([#1579](https://github.com/ethereum/execution-spec-tests/pull/1579)).
66+
-[EIP-7825](https://eips.ethereum.org/EIPS/eip-7825): Add test cases for the transaction gas limit of 30M gas ([#1711](https://github.com/ethereum/execution-spec-tests/pull/1711)).
6667

6768
## [v4.5.0](https://github.com/ethereum/execution-spec-tests/releases/tag/v4.5.0) - 2025-05-14
6869

eels_resolutions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,6 @@
4242
"Osaka": {
4343
"git_url": "https://github.com/spencer-tb/execution-specs.git",
4444
"branch": "forks/osaka",
45-
"commit": "e59c6e3eaed0dbbca639b6f5b6acaa832e51ca00"
45+
"commit": "0d86ad789e7c0d25ec86f15d0e4adb9d9b308af3"
4646
}
4747
}

src/ethereum_clis/clis/besu.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,4 +342,7 @@ class BesuExceptionMapper(ExceptionMapper):
342342
r"expected (\d+), but got (-?\d+)|"
343343
r"Invalid deposit log length\. Must be \d+ bytes, but is \d+ bytes"
344344
),
345+
TransactionException.GAS_LIMIT_EXCEEDS_MAXIMUM: (
346+
r"transaction invalid Transaction gas limit must be at most \d+"
347+
),
345348
}

src/ethereum_clis/clis/erigon.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ class ErigonExceptionMapper(ExceptionMapper):
4242
BlockException.INVALID_BLOCK_HASH: "invalid block hash",
4343
}
4444
mapping_regex = {
45+
TransactionException.GAS_LIMIT_EXCEEDS_MAXIMUM: (
46+
r"invalid block, txnIdx=\d+, gas limit too high"
47+
),
4548
BlockException.INCORRECT_BLOB_GAS_USED: r"blobGasUsed by execution: \d+, in header: \d+",
4649
BlockException.INCORRECT_EXCESS_BLOB_GAS: r"invalid excessBlobGas: have \d+, want \d+",
4750
BlockException.INVALID_GAS_USED: r"gas used by execution: \w+, in header: \w+",

src/ethereum_clis/clis/geth.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ class GethExceptionMapper(ExceptionMapper):
6767
TransactionException.TYPE_4_TX_CONTRACT_CREATION: (
6868
"input string too short for common.Address, decoding into (types.SetCodeTx).To"
6969
),
70+
TransactionException.GAS_LIMIT_EXCEEDS_MAXIMUM: (
71+
"transaction exceeds maximum allowed gas limit"
72+
),
7073
TransactionException.TYPE_4_TX_PRE_FORK: ("transaction type not supported"),
7174
TransactionException.INITCODE_SIZE_EXCEEDED: "max initcode size exceeded",
7275
TransactionException.NONCE_MISMATCH_TOO_LOW: "nonce too low",

src/ethereum_clis/clis/nethermind.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,9 @@ class NethermindExceptionMapper(ExceptionMapper):
377377
r"BlockBlobGasExceeded: A block cannot have more than \d+ blob gas, blobs count \d+, "
378378
r"blobs gas used: \d+"
379379
),
380+
TransactionException.GAS_LIMIT_EXCEEDS_MAXIMUM: (
381+
r"TxGasLimitCapExceeded: Gas limit \d+ \w+ cap of \d+\.?"
382+
),
380383
BlockException.INCORRECT_EXCESS_BLOB_GAS: (
381384
r"HeaderExcessBlobGasMismatch: Excess blob gas in header does not match calculated"
382385
r"|Overflow in excess blob gas"

src/ethereum_clis/clis/nimbus.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ class NimbusExceptionMapper(ExceptionMapper):
8585
TransactionException.TYPE_3_TX_INVALID_BLOB_VERSIONED_HASH: (
8686
"invalid tx: one of blobVersionedHash has invalid version"
8787
),
88+
# TODO: temp solution until mapper for nimbus is fixed
89+
TransactionException.GAS_LIMIT_EXCEEDS_MAXIMUM: ("zero gasUsed but transactions present"),
8890
# This message is the same as TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED
8991
TransactionException.TYPE_3_TX_BLOB_COUNT_EXCEEDED: "exceeds maximum allowance",
9092
TransactionException.TYPE_3_TX_ZERO_BLOBS: "blob transaction missing blob hashes",

src/ethereum_clis/clis/reth.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ class RethExceptionMapper(ExceptionMapper):
5555
TransactionException.GAS_ALLOWANCE_EXCEEDED: (
5656
r"transaction gas limit \w+ is more than blocks available gas \w+"
5757
),
58+
TransactionException.GAS_LIMIT_EXCEEDS_MAXIMUM: (
59+
r"transaction gas limit \(\d+\) is greater than the cap \(\d+\)"
60+
),
5861
BlockException.SYSTEM_CONTRACT_CALL_FAILED: r"failed to apply .* requests contract call",
5962
BlockException.INCORRECT_BLOB_GAS_USED: (
6063
r"blob gas used mismatch|blob gas used \d+ is not a multiple of blob gas per blob"

src/ethereum_test_exceptions/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,10 @@ class TransactionException(ExceptionBase):
363363
"""
364364
Transaction causes block to go over blob gas limit.
365365
"""
366+
GAS_LIMIT_EXCEEDS_MAXIMUM = auto()
367+
"""
368+
Transaction gas limit exceeds the maximum allowed limit of 30 million.
369+
"""
366370
TYPE_3_TX_ZERO_BLOBS = auto()
367371
"""
368372
Transaction is type 3, but has no blobs.

src/ethereum_test_forks/base_fork.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,12 @@ def contract_creating_tx_types(cls, block_number: int = 0, timestamp: int = 0) -
337337
"""Return list of the transaction types supported by the fork that can create contracts."""
338338
pass
339339

340+
@classmethod
341+
@abstractmethod
342+
def transaction_gas_limit_cap(cls, block_number: int = 0, timestamp: int = 0) -> int | None:
343+
"""Return the transaction gas limit cap, or None if no limit is imposed."""
344+
pass
345+
340346
@classmethod
341347
@abstractmethod
342348
def precompiles(cls, block_number: int = 0, timestamp: int = 0) -> List[Address]:

src/ethereum_test_forks/forks/forks.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,11 @@ def contract_creating_tx_types(cls, block_number: int = 0, timestamp: int = 0) -
354354
"""At Genesis, only legacy transactions are allowed."""
355355
return [0]
356356

357+
@classmethod
358+
def transaction_gas_limit_cap(cls, block_number: int = 0, timestamp: int = 0) -> int | None:
359+
"""At Genesis, no transaction gas limit cap is imposed."""
360+
return None
361+
357362
@classmethod
358363
def precompiles(cls, block_number: int = 0, timestamp: int = 0) -> List[Address]:
359364
"""At Genesis, no pre-compiles are present."""
@@ -1339,6 +1344,11 @@ def engine_get_blobs_version(cls, block_number: int = 0, timestamp: int = 0) ->
13391344
"""At Osaka, the engine get blobs version is 2."""
13401345
return 2
13411346

1347+
@classmethod
1348+
def transaction_gas_limit_cap(cls, block_number: int = 0, timestamp: int = 0) -> int | None:
1349+
"""At Osaka, transaction gas limit is capped at 30 million."""
1350+
return 30_000_000
1351+
13421352
@classmethod
13431353
def is_deployed(cls) -> bool:
13441354
"""
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
"""
2+
abstract: Tests [EIP-7825: Transaction Gas Limit Cap](https://eips.ethereum.org/EIPS/eip-7825).
3+
Test cases for [EIP-7825: Transaction Gas Limit Cap](https://eips.ethereum.org/EIPS/eip-7825).
4+
"""
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
"""
2+
abstract: Tests [EIP-7825 Transaction Gas Limit Cap](https://eips.ethereum.org/EIPS/eip-7825)
3+
Test cases for [EIP-7825 Transaction Gas Limit Cap](https://eips.ethereum.org/EIPS/eip-7825)].
4+
"""
5+
6+
from typing import List
7+
8+
import pytest
9+
10+
from ethereum_test_forks import Fork
11+
from ethereum_test_tools import (
12+
Account,
13+
Address,
14+
Alloc,
15+
AuthorizationTuple,
16+
Block,
17+
BlockchainTestFiller,
18+
Environment,
19+
StateTestFiller,
20+
Storage,
21+
Transaction,
22+
TransactionException,
23+
add_kzg_version,
24+
)
25+
from ethereum_test_tools.utility.pytest import ParameterSet
26+
from ethereum_test_tools.vm.opcode import Opcodes as Op
27+
28+
REFERENCE_SPEC_GIT_PATH = "EIPS/eip-7825.md"
29+
REFERENCE_SPEC_VERSION = "47cbfed315988c0bd4d10002c110ae402504cd94"
30+
31+
TX_GAS_LIMIT = 30_000_000
32+
BLOB_COMMITMENT_VERSION_KZG = 1
33+
34+
35+
def tx_gas_limit_cap_tests(fork: Fork) -> List[ParameterSet]:
36+
"""
37+
Return a list of tests for transaction gas limit cap parametrized for each different
38+
fork.
39+
"""
40+
fork_tx_gas_limit_cap = fork.transaction_gas_limit_cap()
41+
if fork_tx_gas_limit_cap is None:
42+
# Use a default value for forks that don't have a transaction gas limit cap
43+
return [
44+
pytest.param(TX_GAS_LIMIT + 1, None, id="tx_gas_limit_cap_none"),
45+
]
46+
47+
return [
48+
pytest.param(
49+
fork_tx_gas_limit_cap + 1,
50+
TransactionException.GAS_LIMIT_EXCEEDS_MAXIMUM,
51+
id="tx_gas_limit_cap_exceeds_maximum",
52+
marks=pytest.mark.exception_test,
53+
),
54+
pytest.param(fork_tx_gas_limit_cap, None, id="tx_gas_limit_cap_none"),
55+
]
56+
57+
58+
@pytest.mark.parametrize_by_fork("tx_gas_limit,error", tx_gas_limit_cap_tests)
59+
@pytest.mark.with_all_tx_types
60+
@pytest.mark.valid_from("Prague")
61+
def test_transaction_gas_limit_cap(
62+
state_test: StateTestFiller,
63+
pre: Alloc,
64+
fork: Fork,
65+
tx_gas_limit: int,
66+
error: TransactionException | None,
67+
tx_type: int,
68+
):
69+
"""Test the transaction gas limit cap behavior for all transaction types."""
70+
env = Environment()
71+
72+
sender = pre.fund_eoa()
73+
storage = Storage()
74+
contract_address = pre.deploy_contract(
75+
code=Op.SSTORE(storage.store_next(1), 1) + Op.STOP,
76+
)
77+
78+
tx_kwargs = {
79+
"ty": tx_type,
80+
"to": contract_address,
81+
"gas_limit": tx_gas_limit,
82+
"data": b"",
83+
"value": 0,
84+
"sender": sender,
85+
"error": error,
86+
}
87+
88+
# Add extra required fields based on transaction type
89+
if tx_type >= 1:
90+
# Type 1: EIP-2930 Access List Transaction
91+
tx_kwargs["access_list"] = [
92+
{
93+
"address": contract_address,
94+
"storage_keys": [0],
95+
}
96+
]
97+
if tx_type == 3:
98+
# Type 3: EIP-4844 Blob Transaction
99+
tx_kwargs["max_fee_per_blob_gas"] = fork.min_base_fee_per_blob_gas()
100+
tx_kwargs["blob_versioned_hashes"] = add_kzg_version([0], BLOB_COMMITMENT_VERSION_KZG)
101+
elif tx_type == 4:
102+
# Type 4: EIP-7702 Set Code Transaction
103+
signer = pre.fund_eoa(amount=0)
104+
tx_kwargs["authorization_list"] = [
105+
AuthorizationTuple(
106+
signer=signer,
107+
address=Address(0),
108+
nonce=0,
109+
)
110+
]
111+
112+
tx = Transaction(**tx_kwargs)
113+
post = {contract_address: Account(storage=storage if error is None else {})}
114+
115+
state_test(env=env, pre=pre, post=post, tx=tx)
116+
117+
118+
@pytest.mark.valid_at_transition_to("Osaka", subsequent_forks=True)
119+
@pytest.mark.exception_test
120+
def test_transaction_gas_limit_cap_at_transition(
121+
blockchain_test: BlockchainTestFiller,
122+
pre: Alloc,
123+
fork: Fork,
124+
):
125+
"""
126+
Test transaction gas limit cap behavior at the Osaka transition.
127+
128+
Before timestamp 15000: No gas limit cap (transactions with gas > 30M are valid)
129+
At/after timestamp 15000: Gas limit cap of 30M is enforced
130+
"""
131+
sender = pre.fund_eoa()
132+
contract_address = pre.deploy_contract(
133+
code=Op.SSTORE(0, Op.ADD(Op.SLOAD(0), 1)) + Op.STOP,
134+
)
135+
136+
pre_cap = fork.transaction_gas_limit_cap()
137+
if pre_cap is None:
138+
pre_cap = TX_GAS_LIMIT
139+
140+
# Transaction with gas limit above 30M
141+
high_gas_tx = Transaction(
142+
ty=0, # Legacy transaction
143+
to=contract_address,
144+
gas_limit=pre_cap + 1,
145+
data=b"",
146+
value=0,
147+
sender=sender,
148+
)
149+
150+
post_cap = fork.transaction_gas_limit_cap(timestamp=15_000)
151+
post_cap_tx_error = TransactionException.GAS_LIMIT_EXCEEDS_MAXIMUM
152+
153+
assert post_cap is not None, "Post cap should not be None"
154+
assert post_cap <= pre_cap, (
155+
"Post cap should be less than or equal to pre cap, test needs update"
156+
)
157+
158+
# Transaction with gas limit at the cap
159+
cap_gas_tx = Transaction(
160+
ty=0, # Legacy transaction
161+
to=contract_address,
162+
gas_limit=post_cap + 1,
163+
data=b"",
164+
value=0,
165+
sender=sender,
166+
error=post_cap_tx_error,
167+
)
168+
169+
blocks = []
170+
171+
# Before transition (timestamp < 15000): high gas transaction should succeed
172+
blocks.append(
173+
Block(
174+
timestamp=14_999,
175+
txs=[high_gas_tx],
176+
)
177+
)
178+
179+
# At transition (timestamp = 15000): high gas transaction should fail
180+
blocks.append(
181+
Block(
182+
timestamp=15_000,
183+
txs=[cap_gas_tx], # Only transaction at the cap succeeds
184+
exception=post_cap_tx_error,
185+
)
186+
)
187+
188+
# Post state: storage should be updated by successful transactions
189+
post = {
190+
contract_address: Account(
191+
storage={
192+
0: 1, # Set by first transaction (before transition)
193+
}
194+
)
195+
}
196+
197+
blockchain_test(pre=pre, blocks=blocks, post=post)

0 commit comments

Comments
 (0)