Skip to content

test_sstore_stipend_check_excludes_reservoir()

Documentation for tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py::test_sstore_stipend_check_excludes_reservoir@87aba1a3.

Generate fixtures for these test cases for Amsterdam with:

fill -v tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py::test_sstore_stipend_check_excludes_reservoir --fork Amsterdam

Verify the SSTORE gas check uses gas_left only, not the reservoir.

A child frame has gas_left at or just below the stipend threshold (GAS_CALL_STIPEND + 1) while the reservoir holds ample state gas. The check must fail when gas_left is too low, regardless of the reservoir balance.

Post-8038 the cold access cost (COLD_STORAGE_ACCESS = 3000) exceeds the stipend (2300), so for this cold slot the access cost is the binding gate and the stipend sentry is subsumed. The reservoir is excluded either way, which is what this test pins down.

With below_stipend: SSTORE fails (gas_left too low, reservoir ignored). With at_stipend: SSTORE has full regular gas and proceeds.

Source code in tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
@pytest.mark.parametrize(
    "gas_above_stipend",
    [
        pytest.param(-1, id="below_stipend"),
        pytest.param(0, id="at_stipend"),
    ],
)
@pytest.mark.valid_from("EIP8037")
def test_sstore_stipend_check_excludes_reservoir(
    state_test: StateTestFiller,
    pre: Alloc,
    fork: Fork,
    gas_above_stipend: int,
) -> None:
    """
    Verify the SSTORE gas check uses gas_left only, not the reservoir.

    A child frame has gas_left at or just below the stipend threshold
    (GAS_CALL_STIPEND + 1) while the reservoir holds ample state gas.
    The check must fail when gas_left is too low, regardless of the
    reservoir balance.

    Post-8038 the cold access cost (COLD_STORAGE_ACCESS = 3000) exceeds
    the stipend (2300), so for this cold slot the access cost is the
    binding gate and the stipend sentry is subsumed. The reservoir is
    excluded either way, which is what this test pins down.

    With below_stipend: SSTORE fails (gas_left too low, reservoir ignored).
    With at_stipend: SSTORE has full regular gas and proceeds.
    """
    gas_costs = fork.gas_costs()
    stipend = gas_costs.CALL_STIPEND + 1
    sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork)

    # Child: Op.SSTORE(0, 1) = 2 pushes + SSTORE opcode.
    child_code = Op.SSTORE(0, 1)
    child = pre.deploy_contract(child_code)

    # Full regular gas for the child (pushes + SSTORE regular cost).
    # State gas comes from the reservoir so it doesn't affect gas_left.
    child_full_regular = child_code.gas_cost(fork) - sstore_state_gas

    # below_stipend: give 1 less than stipend after pushes, fails check.
    # at_stipend: give full regular gas, passes check and completes.
    if gas_above_stipend < 0:
        push_gas = 2 * gas_costs.VERY_LOW
        child_gas = push_gas + stipend - 1
    else:
        child_gas = child_full_regular

    # Caller forwards limited regular gas via CALL. State gas comes
    # from the reservoir (gas_limit above the cap).
    caller_storage = Storage()
    sstore_succeeds = gas_above_stipend >= 0
    caller = pre.deploy_contract(
        Op.SSTORE(
            caller_storage.store_next(
                1 if sstore_succeeds else 0,
                "sstore_succeeds"
                if sstore_succeeds
                else "sstore_fails_stipend",
            ),
            Op.CALL(gas=child_gas, address=child),
        )
    )

    tx = Transaction(
        sender=pre.fund_eoa(),
        to=caller,
        state_gas_reservoir=sstore_state_gas,
    )

    post = {caller: Account(storage=caller_storage)}
    state_test(pre=pre, tx=tx, post=post)

Parametrized Test Cases

This test generates 2 parametrized test cases across 1 fork.