@@ -409,104 +409,92 @@ def test_create_tx_intrinsic_gas_boundary(
409409 state_test (pre = pre , post = {}, tx = tx )
410410
411411
412- # TODO(diff-at-return): Code deposit is now part of the state diff
413- # at CREATE's call return, not a separate charge. This test's
414- # concept of "code deposit OOG" changes — the CREATE call either
415- # has enough reservoir for the full diff (account + code) or it
416- # reverts entirely. Need to rethink this test.
417412@pytest .mark .valid_from ("EIP8037" )
418- def test_code_deposit_oog_preserves_parent_reservoir (
413+ def test_caller_reservoir_preserved_after_callee_diff_reverts (
419414 state_test : StateTestFiller ,
420415 pre : Alloc ,
421416 fork : Fork ,
422417) -> None :
423418 """
424- Test parent reservoir preserved after child code deposit OOG.
425-
426- A caller contract invokes the factory via CALL with limited gas.
427- The child CREATE returns enough bytes that code deposit state gas
428- exceeds the child frame's available gas (reservoir spillover plus
429- the limited gas_left). The factory's SSTORE after the failed
430- CREATE proves the reservoir was not inflated by a spill-then-halt
431- refund .
419+ Test caller reservoir preserved when callee reverts due to
420+ state diff exceeding its budget.
421+
422+ The caller CALLs a factory with limited gas. The factory does
423+ CREATE deploying large code. At the factory's call return, the
424+ state diff (account + code) exceeds the factory's budget, so the
425+ factory reverts. The caller's reservoir is preserved and can be
426+ used for a subsequent SSTORE .
432427 """
433428 gas_limit_cap = fork .transaction_gas_limit_cap ()
434429 assert gas_limit_cap is not None
435- gas_costs = fork .gas_costs ()
436- new_account_state_gas = gas_costs .GAS_NEW_ACCOUNT
437430 sstore_state_gas = fork .sstore_state_gas ()
438431
439- # Small deploy size; code deposit state gas will exceed the
440- # limited gas available in the CREATE child frame.
441432 deploy_size = 4096
442433 init_code = Op .RETURN (0 , deploy_size )
443434
444- # Limited regular gas forwarded to the factory. After CREATE
445- # takes 63/64, the factory retains ~15 K for its SSTOREs.
446- child_gas = 1_000_000
447-
448- factory_storage = Storage ()
449435 factory = pre .deploy_contract (
450436 code = (
451437 Op .MSTORE (0 , Op .PUSH32 (bytes (init_code )))
452- + Op .SSTORE (
453- factory_storage .store_next (0 , "create_fails" ),
438+ + Op .POP (
454439 Op .CREATE (
455440 value = 0 ,
456441 offset = 32 - len (init_code ),
457442 size = len (init_code ),
458443 ),
459444 )
460- # Reservoir must be fully preserved after failed CREATE;
461- # parent can still perform its own SSTORE.
462- + Op .SSTORE (
463- factory_storage .store_next (1 , "parent_sstore" ),
464- 1 ,
465- )
466445 ),
467446 )
468447
469- # Caller invokes factory with limited gas via CALL.
448+ # Caller: CALL factory with limited gas (factory will revert
449+ # because its diff is too expensive). Then SSTORE to prove
450+ # the caller's reservoir is preserved.
451+ caller_storage = Storage ()
470452 caller = pre .deploy_contract (
471- code = Op .CALL (gas = child_gas , address = factory ),
453+ code = (
454+ Op .POP (Op .CALL (gas = 1_000_000 , address = factory ))
455+ + Op .SSTORE (caller_storage .store_next (1 , "sstore_ok" ), 1 )
456+ ),
472457 )
473458
474- # Reservoir = new-account state gas + one SSTORE's state gas.
475- # Code deposit draws from the reservoir first then spills into
476- # gas_left, which the limited CALL gas cannot cover.
477459 tx = Transaction (
478460 to = caller ,
479- gas_limit = ( gas_limit_cap + new_account_state_gas + sstore_state_gas ) ,
461+ gas_limit = gas_limit_cap + sstore_state_gas ,
480462 sender = pre .fund_eoa (),
481463 )
482464
483- post = {factory : Account (storage = factory_storage )}
465+ post = {caller : Account (storage = caller_storage )}
484466 state_test (pre = pre , post = post , tx = tx )
485467
486468
487- # TODO(diff-at-return): Code deposit is now part of the state diff.
488- # The concept of "borrowing parent gas for code deposit" no longer
489- # applies — the CREATE call's diff includes code bytes and either
490- # the reservoir covers it or the call reverts.
491469@pytest .mark .valid_from ("EIP8037" )
492- def test_nested_create_code_deposit_cannot_borrow_parent_gas (
470+ def test_parent_reverts_when_create_diff_exceeds_budget (
493471 state_test : StateTestFiller ,
494472 pre : Alloc ,
495473 fork : Fork ,
496474) -> None :
497475 """
498- Test nested CREATE code deposit does not borrow parent gas .
476+ Test parent call reverts when CREATE's state diff exceeds budget .
499477
500- Provide just enough gas for CREATE to start (new account state
501- gas + regular gas) but not enough for the child frame to cover
502- code deposit after init code runs. The CREATE increments the
503- factory nonce but code deposit fails, so no contract is deployed.
478+ With diff-at-return, account creation and code deployment from a
479+ CREATE are part of the parent's state diff (they happen outside
480+ the CREATE child's process_message). The parent pays at its own
481+ call return. If the parent's total budget (reservoir + gas_left)
482+ cannot cover the diff, the parent's call reverts.
483+
484+ Uses a large deploy size so the code deposit state gas exceeds
485+ the available budget (reservoir + gas_left spillover).
504486 """
505- init_code = Op .RETURN (0 , 1 )
506- gas_costs = fork .gas_costs ()
507- new_acct_state = gas_costs .GAS_NEW_ACCOUNT
508- code_deposit_state = fork .code_deposit_state_gas (code_size = 1 )
487+ gas_limit_cap = fork .transaction_gas_limit_cap ()
488+ assert gas_limit_cap is not None
509489
490+ # 15000 bytes of code: state cost = (112 + 15000) * cpsb ≈ 17.7M
491+ # TX_MAX_GAS_LIMIT = 16.7M, so total budget can't cover it
492+ # when reservoir = 0 (gas_limit at cap).
493+ deploy_size = 15000
494+ init_code = Op .RETURN (0 , deploy_size )
495+
496+ # Wrapper calls factory. Factory does CREATE. If the wrapper's
497+ # diff at return exceeds its budget, the wrapper reverts.
510498 factory = pre .deploy_contract (
511499 code = (
512500 Op .MSTORE (0 , Op .PUSH32 (bytes (init_code )))
@@ -519,42 +507,31 @@ def test_nested_create_code_deposit_cannot_borrow_parent_gas(
519507 )
520508 ),
521509 )
522- created = compute_create_address (address = factory , nonce = 1 )
523-
524- # Gas consumed before the child CREATE frame receives gas:
525- # Intrinsic + factory code (PUSH32+PUSH1+MSTORE+mem +
526- # 3xPUSH1) + CREATE regular (+ init_code_cost) + new account
527- # state gas (spilled from gas_left, no reservoir).
528- init_code_word_cost = gas_costs .GAS_CODE_INIT_PER_WORD * (
529- (len (init_code ) + 31 ) // 32
530- )
531- pre_child_gas = (
532- gas_costs .GAS_TX_BASE
533- + 7 * gas_costs .GAS_VERY_LOW
534- + gas_costs .GAS_MEMORY
535- + (gas_costs .GAS_CREATE - new_acct_state )
536- + init_code_word_cost
537- + new_acct_state
538- )
539510
540- # Init code cost: PUSH1 + PUSH1 + RETURN(+mem expansion)
541- init_cost = 2 * gas_costs .GAS_VERY_LOW + gas_costs .GAS_MEMORY
542- # Target child gas: enough for init, not enough for code deposit
543- target_child = (init_cost + code_deposit_state ) // 2
544- # Invert EIP-150 63/64ths rule: ceil(target_child * 64 / 63)
545- factory_remaining = (target_child * 64 + 62 ) // 63
546- gas_limit = pre_child_gas + factory_remaining
511+ storage = Storage ()
512+ wrapper = pre .deploy_contract (
513+ code = (
514+ # CALL returns 1 on success, 0 on failure.
515+ # Store the result to verify the factory reverted.
516+ Op .SSTORE (
517+ storage .store_next (0 , "call_failed" ),
518+ Op .CALL (gas = Op .GAS , address = factory ),
519+ )
520+ ),
521+ )
547522
523+ # gas_limit at cap = reservoir 0. Total budget = gas_left only.
524+ # State diff cost (17.7M) > TX_MAX_GAS_LIMIT (16.7M).
548525 tx = Transaction (
549- to = factory ,
550- gas_limit = gas_limit ,
526+ to = wrapper ,
527+ gas_limit = gas_limit_cap ,
551528 sender = pre .fund_eoa (),
552529 )
553530
554- post = {
555- factory : Account ( nonce = 2 ),
556- created : Account . NONEXISTENT ,
557- }
531+ # Wrapper's CALL to factory should fail (diff too expensive).
532+ # The SSTORE stores 0 (CALL returned 0) not 1.
533+ # But wrapper itself continues — only the inner CALL reverted.
534+ post = { wrapper : Account ( storage = storage ) }
558535 state_test (pre = pre , post = post , tx = tx )
559536
560537
0 commit comments