1717from execution_testing import (
1818 Account ,
1919 Alloc ,
20+ Block ,
21+ BlockchainTestFiller ,
2022 Fork ,
23+ Header ,
2124 Initcode ,
2225 Op ,
2326 StateTestFiller ,
3033REFERENCE_SPEC_GIT_PATH = ref_spec_8037 .git_path
3134REFERENCE_SPEC_VERSION = ref_spec_8037 .version
3235
36+ WORD_SIZE = 32
37+
3338
3439def _single_sstore_probe_gas (fork : Fork ) -> int :
3540 """
@@ -253,36 +258,61 @@ def test_selfdestruct_oog_reservoir_inflation_detection(
253258 state_test (pre = pre , tx = tx , post = post )
254259
255260
261+ @pytest .mark .parametrize (
262+ "oog_step" ,
263+ [
264+ pytest .param ("create_base" , id = "oog_on_create_base" ),
265+ pytest .param ("init_code_word_cost" , id = "oog_on_init_code_word_cost" ),
266+ ],
267+ )
256268@pytest .mark .with_all_create_opcodes ()
257269@pytest .mark .valid_from ("EIP8037" )
258270def test_create_oog_reservoir_inflation_detection (
259271 state_test : StateTestFiller ,
260272 pre : Alloc ,
261273 fork : Fork ,
262274 create_opcode : Op ,
275+ oog_step : str ,
263276) -> None :
264277 """
265- Detect CREATE/CREATE2 state gas ordering via reservoir inflation.
266-
267- A child does CREATE (or CREATE2) with size=0 and gas tuned so the
268- regular gas charge OOGs by 1. CREATE/CREATE2 already have the
269- correct ordering (regular before state), so this is a regression
270- test ensuring it stays that way.
271-
272- Single-SSTORE probe detects potential inflation.
278+ Detect CREATE/CREATE2 state-gas ordering via parent-reservoir
279+ inflation. Two OOG boundaries are exercised: `oog_on_create_base`
280+ (empty initcode) and `oog_on_init_code_word_cost` (32-byte
281+ initcode).
273282 """
274283 gas_costs = fork .gas_costs ()
275284 new_account_state_gas = gas_costs .NEW_ACCOUNT
276285
286+ if oog_step == "create_base" :
287+ initcode_size = 0
288+ setup_gas = 0
289+ init_code_word_cost = 0
290+ else :
291+ initcode_size = WORD_SIZE
292+ setup_gas = (
293+ Op .MSTORE .popped_stack_items * gas_costs .VERY_LOW
294+ + gas_costs .OPCODE_MSTORE_BASE
295+ + gas_costs .MEMORY_PER_WORD
296+ )
297+ init_code_word_cost = gas_costs .CODE_INIT_PER_WORD
298+
277299 if create_opcode == Op .CREATE :
278- child_code = create_opcode (value = 0 , offset = 0 , size = 0 )
279- pushes_gas = 3 * gas_costs .VERY_LOW
300+ create_op = create_opcode (value = 0 , offset = 0 , size = initcode_size )
280301 else :
281- child_code = create_opcode (value = 0 , offset = 0 , size = 0 , salt = 0 )
282- pushes_gas = 4 * gas_costs .VERY_LOW
302+ create_op = create_opcode (
303+ value = 0 , offset = 0 , size = initcode_size , salt = 0
304+ )
305+ pushes_gas = create_opcode .popped_stack_items * gas_costs .VERY_LOW
283306
284- create_regular_gas = gas_costs .OPCODE_CREATE_BASE
285- child_gas = pushes_gas + create_regular_gas + new_account_state_gas - 1
307+ if oog_step == "create_base" :
308+ child_code = create_op
309+ else :
310+ child_code = Op .MSTORE (0 , 0 ) + create_op
311+
312+ create_regular_gas = gas_costs .OPCODE_CREATE_BASE + init_code_word_cost
313+ child_gas = (
314+ setup_gas + pushes_gas + create_regular_gas + new_account_state_gas - 1
315+ )
286316 child = pre .deploy_contract (child_code )
287317
288318 probe = pre .deploy_contract (Op .SSTORE (0 , 1 ))
@@ -306,3 +336,79 @@ def test_create_oog_reservoir_inflation_detection(
306336
307337 post = {caller : Account (storage = caller_storage )}
308338 state_test (pre = pre , tx = tx , post = post )
339+
340+
341+ @pytest .mark .parametrize (
342+ "oog_step" ,
343+ [
344+ pytest .param ("create_base" , id = "oog_on_create_base" ),
345+ pytest .param ("init_code_word_cost" , id = "oog_on_init_code_word_cost" ),
346+ ],
347+ )
348+ @pytest .mark .with_all_create_opcodes ()
349+ @pytest .mark .valid_from ("EIP8037" )
350+ def test_create_oog_full_burn_no_state_credit (
351+ blockchain_test : BlockchainTestFiller ,
352+ pre : Alloc ,
353+ fork : Fork ,
354+ create_opcode : Op ,
355+ oog_step : str ,
356+ ) -> None :
357+ """
358+ Verify a CREATE OOG inside a non-creation tx burns the whole
359+ tx gas_limit — no state-gas leftover is credited at tx-end.
360+ """
361+ gas_costs = fork .gas_costs ()
362+ new_account_state_gas = gas_costs .NEW_ACCOUNT
363+
364+ if oog_step == "create_base" :
365+ initcode_size = 0
366+ setup_gas = 0
367+ init_code_word_cost = 0
368+ else :
369+ initcode_size = WORD_SIZE
370+ setup_gas = (
371+ 2 * gas_costs .VERY_LOW
372+ + gas_costs .OPCODE_MSTORE_BASE
373+ + gas_costs .MEMORY_PER_WORD
374+ )
375+ init_code_word_cost = gas_costs .CODE_INIT_PER_WORD
376+
377+ if create_opcode == Op .CREATE :
378+ create_op = create_opcode (value = 0 , offset = 0 , size = initcode_size )
379+ else :
380+ create_op = create_opcode (
381+ value = 0 , offset = 0 , size = initcode_size , salt = 0
382+ )
383+ pushes_gas = create_opcode .popped_stack_items * gas_costs .VERY_LOW
384+
385+ if oog_step == "create_base" :
386+ factory_code = create_op
387+ else :
388+ factory_code = Op .MSTORE (0 , 0 ) + create_op
389+ factory = pre .deploy_contract (factory_code )
390+
391+ create_regular_gas = gas_costs .OPCODE_CREATE_BASE + init_code_word_cost
392+ body_gas = (
393+ setup_gas + pushes_gas + create_regular_gas + new_account_state_gas - 1
394+ )
395+
396+ intrinsic_calc = fork .transaction_intrinsic_cost_calculator ()
397+ tx_gas_limit = intrinsic_calc () + body_gas
398+
399+ tx = Transaction (
400+ sender = pre .fund_eoa (),
401+ to = factory ,
402+ gas_limit = tx_gas_limit ,
403+ )
404+
405+ blockchain_test (
406+ pre = pre ,
407+ blocks = [
408+ Block (
409+ txs = [tx ],
410+ header_verify = Header (gas_used = tx_gas_limit ),
411+ ),
412+ ],
413+ post = {},
414+ )
0 commit comments