Skip to content

Commit 73571dd

Browse files
authored
feat(tests): expand *CALL gas testing test (#1703)
1 parent 15b5cfd commit 73571dd

File tree

2 files changed

+132
-47
lines changed

2 files changed

+132
-47
lines changed

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Test fixtures for use by clients are available for each release on the [Github r
2424

2525
- 🐞 Fix BALs opcode OOG test vectors by updating the Amsterdam commit hash in specs and validating appropriately on the testing side ([#2293](https://github.com/ethereum/execution-spec-tests/pull/2293)).
2626
- ✨ Fix test vector for BALs SSTORE with OOG by pointing to updated specs; add new boundary conditions cases for SSTORE w/ OOG ([#2297](https://github.com/ethereum/execution-spec-tests/pull/2297)).
27+
- ✨ Expand cases to test *CALL opcodes causing OOG ([#1703](https://github.com/ethereum/execution-specs/pull/1703)).
2728

2829
## [v5.3.0](https://github.com/ethereum/execution-spec-tests/releases/tag/v5.3.0) - 2025-10-09
2930

tests/frontier/opcodes/test_call_and_callcode_gas_calculation.py

Lines changed: 131 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
"""
2-
Tests nested CALL/CALLCODE gas usage with positive value transfer.
2+
Tests nested CALL/CALLCODE/DELEGATECALL/STATICCALL gas usage with positive
3+
value transfer (where applicable).
34
45
This test investigates an issue identified in EthereumJS, as reported in:
56
https://github.com/ethereumjs/ethereumjs-monorepo/issues/3194.
67
7-
The issue pertains to the incorrect gas calculation for CALL/CALLCODE
8-
operations with a positive value transfer, due to the pre-addition of the
9-
gas stipend (2300) to the currently available gas instead of adding it to
10-
the new call frame. This bug was specific to the case where insufficient
11-
gas was provided for the CALL/CALLCODE operation. Due to the pre-addition
12-
of the stipend to the currently available gas, the case for insufficient
13-
gas was not properly failing with an out-of-gas error.
8+
The issue pertains to the incorrect gas calculation for
9+
CALL/CALLCODE/DELEGATECALL/STATICCALL operations with a positive value
10+
transfer, due to the pre-addition of the gas stipend (2300) to the currently
11+
available gas instead of adding it to the new call frame. This bug was specific
12+
to the case where insufficient gas was provided for the CALL/CALLCODE
13+
operation. Due to the pre-addition of the stipend to the currently available
14+
gas, the case for insufficient gas was not properly failing with an out-of-gas
15+
error.
1416
1517
Test setup:
1618
@@ -44,45 +46,86 @@
4446
StateTestFiller,
4547
Transaction,
4648
)
49+
from execution_testing.forks.forks.forks import Berlin, Byzantium, Homestead
50+
from execution_testing.forks.helpers import Fork
4751

48-
"""
49-
PUSH opcode cost is 3, GAS opcode cost is 2.
50-
We need 6 PUSH's and one GAS to fill the stack for both CALL & CALLCODE,
51-
in the callee contract.
52-
"""
53-
CALLEE_INIT_STACK_GAS = 6 * 3 + 2
5452

55-
"""
56-
CALL gas breakdowns: (https://www.evm.codes/#f1)
57-
memory_exp_cost + code_exec_cost + address_access_cost +
58-
positive_value_cost + empty_account_cost
59-
= 0 + 0 + 2600 + 9000 + 25000 = 36600
60-
"""
61-
CALL_GAS = 36600
62-
CALL_SUFFICIENT_GAS = CALL_GAS + CALLEE_INIT_STACK_GAS
53+
@pytest.fixture
54+
def callee_init_stack_gas(callee_opcode: Op, fork: Fork) -> int:
55+
"""
56+
Calculate the initial stack gas for the callee opcode.
57+
"""
58+
if fork < Byzantium:
59+
# all *CALL arguments handled with PUSHes
60+
return len(callee_opcode.kwargs) * 3
61+
else:
62+
# gas argument handled with GAS which is cheaper
63+
return (len(callee_opcode.kwargs) - 1) * 3 + 2
6364

64-
"""
65-
CALLCODE gas breakdowns: (https://www.evm.codes/#f2)
66-
memory_exp_cost + code_exec_cost + address_access_cost +
67-
positive_value_cost = 0 + 0 + 2600 + 9000 = 11600
68-
"""
69-
CALLCODE_GAS = 11600
70-
CALLCODE_SUFFICIENT_GAS = CALLCODE_GAS + CALLEE_INIT_STACK_GAS
65+
66+
@pytest.fixture
67+
def sufficient_gas(
68+
callee_opcode: Op, callee_init_stack_gas: int, fork: Fork
69+
) -> int:
70+
"""
71+
Calculate the sufficient gas for the nested call opcode with positive
72+
value transfer.
73+
"""
74+
# memory_exp_cost is zero for our case.
75+
cost = 0
76+
77+
if fork >= Berlin:
78+
cost += 2600 # call and address_access_cost
79+
elif Byzantium <= fork < Berlin:
80+
cost += 700 # call
81+
elif fork == Homestead:
82+
cost += 40 # call
83+
cost += 1 # mandatory callee gas allowance
84+
else:
85+
raise Exception("Only forks Homestead and >=Byzantium supported")
86+
87+
is_value_call = callee_opcode in [Op.CALL, Op.CALLCODE]
88+
if is_value_call:
89+
cost += 9000 # positive_value_cost
90+
91+
if callee_opcode == Op.CALL:
92+
cost += 25000 # empty_account_cost
93+
94+
cost += callee_init_stack_gas
95+
96+
return cost
7197

7298

7399
@pytest.fixture
74-
def callee_code(pre: Alloc, callee_opcode: Op) -> Bytecode:
100+
def callee_code(pre: Alloc, callee_opcode: Op, fork: Fork) -> Bytecode:
75101
"""
76102
Code called by the caller contract:
77103
PUSH1 0x00 * 4
78-
PUSH1 0x01 <- for positive value transfer
104+
PUSH1 0x01 <- for positive value transfer, if applies
79105
PUSH2 Contract.nonexistent
80-
GAS <- value doesn't matter
81-
CALL/CALLCODE.
106+
PUSH1 0x00 or GAS <- value doesn't matter:
107+
- PUSH1 0x01: pre Byzantium tests, as they require `gas_left` to be at
108+
least `gas`. In these cases we want non-zero `gas`, so that that
109+
condition check can also be triggered - see `gas_shortage` parameter
110+
values.
111+
- GAS: other forks, to not alter previous test behavior
112+
CALL/CALLCODE/DELEGATECALL/STATICCALL.
82113
"""
83114
# The address needs to be empty and different for each execution of the
84115
# fixture, otherwise the calculations (empty_account_cost) are incorrect.
85-
return callee_opcode(Op.GAS(), pre.empty_account(), 1, 0, 0, 0, 0)
116+
is_value_call = callee_opcode in [Op.CALL, Op.CALLCODE]
117+
extra_args = {"value": 1} if is_value_call else {}
118+
119+
return callee_opcode(
120+
unchecked=False,
121+
gas=1 if fork < Byzantium else Op.GAS,
122+
address=pre.empty_account(),
123+
args_offset=0,
124+
args_size=0,
125+
ret_offset=0,
126+
ret_size=0,
127+
**extra_args,
128+
)
86129

87130

88131
@pytest.fixture
@@ -98,7 +141,9 @@ def callee_address(pre: Alloc, callee_code: Bytecode) -> Address:
98141

99142

100143
@pytest.fixture
101-
def caller_code(caller_gas_limit: int, callee_address: Address) -> Bytecode:
144+
def caller_code(
145+
sufficient_gas: int, gas_shortage: int, callee_address: Address
146+
) -> Bytecode:
102147
"""
103148
Code to CALL the callee contract:
104149
PUSH1 0x00 * 5
@@ -108,6 +153,8 @@ def caller_code(caller_gas_limit: int, callee_address: Address) -> Bytecode:
108153
PUSH1 0x00
109154
SSTORE.
110155
"""
156+
caller_gas_limit = sufficient_gas - gas_shortage
157+
111158
return Op.SSTORE(
112159
0, Op.CALL(caller_gas_limit, callee_address, 0, 0, 0, 0, 0)
113160
)
@@ -128,36 +175,32 @@ def caller_address(pre: Alloc, caller_code: Bytecode) -> Address:
128175

129176

130177
@pytest.fixture
131-
def caller_tx(sender: EOA, caller_address: Address) -> Transaction:
178+
def caller_tx(sender: EOA, caller_address: Address, fork: Fork) -> Transaction:
132179
"""Transaction that performs the call to the caller contract."""
133180
return Transaction(
134181
to=caller_address,
135182
value=1,
136183
gas_limit=500_000,
137184
sender=sender,
185+
protected=fork >= Byzantium,
138186
)
139187

140188

141189
@pytest.fixture
142190
def post( # noqa: D103
143-
caller_address: Address, is_sufficient_gas: bool
191+
caller_address: Address, gas_shortage: int
144192
) -> Dict[Address, Account]:
145193
return {
146194
caller_address: Account(
147-
storage={0x00: 0x01 if is_sufficient_gas else 0x00}
195+
storage={0x00: 0x01 if gas_shortage == 0 else 0x00}
148196
),
149197
}
150198

151199

152200
@pytest.mark.parametrize(
153-
"callee_opcode, caller_gas_limit, is_sufficient_gas",
154-
[
155-
(Op.CALL, CALL_SUFFICIENT_GAS, True),
156-
(Op.CALL, CALL_SUFFICIENT_GAS - 1, False),
157-
(Op.CALLCODE, CALLCODE_SUFFICIENT_GAS, True),
158-
(Op.CALLCODE, CALLCODE_SUFFICIENT_GAS - 1, False),
159-
],
201+
"callee_opcode", [Op.CALL, Op.CALLCODE, Op.DELEGATECALL, Op.STATICCALL]
160202
)
203+
@pytest.mark.parametrize("gas_shortage", [0, 1])
161204
@pytest.mark.valid_from("London")
162205
def test_value_transfer_gas_calculation(
163206
state_test: StateTestFiller,
@@ -166,7 +209,48 @@ def test_value_transfer_gas_calculation(
166209
post: Dict[str, Account],
167210
) -> None:
168211
"""
169-
Tests the nested CALL/CALLCODE opcode gas consumption with a positive value
170-
transfer.
212+
Tests the nested CALL/CALLCODE/DELEGATECALL/STATICCALL opcode gas
213+
consumption with a positive value transfer.
214+
"""
215+
state_test(env=Environment(), pre=pre, post=post, tx=caller_tx)
216+
217+
218+
@pytest.mark.parametrize(
219+
"callee_opcode", [Op.CALL, Op.CALLCODE, Op.DELEGATECALL, Op.STATICCALL]
220+
)
221+
@pytest.mark.parametrize("gas_shortage", [0, 1])
222+
@pytest.mark.valid_from("Byzantium")
223+
@pytest.mark.valid_until("Berlin")
224+
def test_value_transfer_gas_calculation_byzantium(
225+
state_test: StateTestFiller,
226+
pre: Alloc,
227+
caller_tx: Transaction,
228+
post: Dict[str, Account],
229+
) -> None:
230+
"""
231+
Tests the nested CALL/CALLCODE/DELEGATECALL/STATICCALL opcode gas
232+
consumption with a positive value transfer.
233+
"""
234+
state_test(env=Environment(), pre=pre, post=post, tx=caller_tx)
235+
236+
237+
@pytest.mark.parametrize(
238+
"callee_opcode", [Op.CALL, Op.CALLCODE, Op.DELEGATECALL]
239+
)
240+
# pre-Byzantium rules have one more condition to fail on:
241+
# the check for `gas_left` to be at least `gas` allowance specified
242+
# in the CALL. We will be setting that allowance to `1` and either
243+
# making the call miss that amount or fail on the earlier gas check.
244+
@pytest.mark.parametrize("gas_shortage", [0, 1, 2])
245+
@pytest.mark.valid_at("Homestead")
246+
def test_value_transfer_gas_calculation_homestead(
247+
state_test: StateTestFiller,
248+
pre: Alloc,
249+
caller_tx: Transaction,
250+
post: Dict[str, Account],
251+
) -> None:
252+
"""
253+
Tests the nested CALL/CALLCODE/DELEGATECALL opcode gas
254+
consumption with a positive value transfer.
171255
"""
172256
state_test(env=Environment(), pre=pre, post=post, tx=caller_tx)

0 commit comments

Comments
 (0)