Skip to content

Commit 10b0044

Browse files
authored
feat(test): add test for memory expansion on early revert in CALL (#1967)
* add initial test version * fix test and add comments
1 parent 66db99d commit 10b0044

File tree

1 file changed

+72
-0
lines changed

1 file changed

+72
-0
lines changed

tests/frontier/opcodes/test_call.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,75 @@ def test_call_large_offset_mstore(
7575
)
7676
},
7777
)
78+
79+
80+
# TODO: There's an issue with gas definitions on forks previous to Berlin, remove this when fixed.
81+
# https://github.com/ethereum/execution-spec-tests/pull/1952#discussion_r2237634275
82+
@pytest.mark.valid_from("Berlin")
83+
def test_call_memory_expands_on_early_revert(
84+
state_test: StateTestFiller,
85+
pre: Alloc,
86+
fork: Fork,
87+
):
88+
"""
89+
When CALL reverts early (e.g. because of not enough balance by the sender),
90+
memory should be expanded anyway.
91+
We check this with an MSTORE.
92+
93+
This is for a bug in an EVM implementation where memory is expanded after executing a CALL, but
94+
not when an early revert happens.
95+
"""
96+
sender = pre.fund_eoa()
97+
98+
gsc = fork.gas_costs()
99+
ret_size = 128 # arbitrary number, greater than memory size to trigger an expansion
100+
101+
call_measure = CodeGasMeasure(
102+
code=Op.CALL(gas=0, value=100, ret_size=ret_size), # CALL with value
103+
overhead_cost=gsc.G_VERY_LOW * len(Op.CALL.kwargs), # Cost of pushing CALL args
104+
extra_stack_items=1, # Because CALL pushes 1 item to the stack
105+
sstore_key=0,
106+
stop=False, # Because it's the first CodeGasMeasure
107+
)
108+
mstore_measure = CodeGasMeasure(
109+
code=Op.MSTORE(offset=ret_size // 2, value=1), # Low offset for not expanding memory
110+
overhead_cost=gsc.G_VERY_LOW * len(Op.MSTORE.kwargs), # Cost of pushing MSTORE args
111+
extra_stack_items=0,
112+
sstore_key=1,
113+
)
114+
115+
# Contract without enough balance to send value transfer
116+
contract = pre.deploy_contract(code=call_measure + mstore_measure, balance=0)
117+
118+
tx = Transaction(
119+
gas_limit=500_000,
120+
to=contract,
121+
value=0,
122+
sender=sender,
123+
)
124+
125+
memory_expansion_gas_calc = fork.memory_expansion_gas_calculator()
126+
# call cost: address_access_cost + new_acc_cost + memory_expansion_cost + value - stipend
127+
call_cost = (
128+
gsc.G_COLD_ACCOUNT_ACCESS
129+
+ gsc.G_NEW_ACCOUNT
130+
+ memory_expansion_gas_calc(new_bytes=ret_size)
131+
+ gsc.G_CALL_VALUE
132+
- gsc.G_CALL_STIPEND
133+
)
134+
135+
# mstore cost: base cost. No memory expansion cost needed, it was expanded on CALL.
136+
mstore_cost = gsc.G_MEMORY
137+
state_test(
138+
env=Environment(),
139+
pre=pre,
140+
tx=tx,
141+
post={
142+
contract: Account(
143+
storage={
144+
0: call_cost,
145+
1: mstore_cost,
146+
},
147+
)
148+
},
149+
)

0 commit comments

Comments
 (0)