Skip to content

test_sstore_write_cost_at_transition()

Documentation for tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py::test_sstore_write_cost_at_transition@26332146.

Generate fixtures for these test cases for Amsterdam with:

fill -v tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py::test_sstore_write_cost_at_transition --fork Amsterdam

The SSTORE first-change cost is repriced across the Amsterdam boundary, and EIP-8038 changes the model, not a single number.

Before the fork (parent schedule) a zero-to-nonzero SSTORE is a flat execution charge (COLD_STORAGE_ACCESS + STORAGE_SET) with no state-gas dimension. After the fork the charge splits: the execution portion drops to COLD_STORAGE_ACCESS + STORAGE_WRITE while the bulk moves into the new state-gas dimension, and the clear refund rises. Every magnitude is derived from the two schedules; nothing is hardcoded.

The transition is asserted at the derived-constant level (the runtime opcode cost cannot isolate the execution portion without the state-gas confounder) and a zero-to-nonzero SSTORE is exercised in both blocks to prove it still sets the slot in each regime.

Source code in tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
@EIPChecklist.GasCostChanges.Test.ForkTransition.Before()
@EIPChecklist.GasCostChanges.Test.ForkTransition.After()
def test_sstore_write_cost_at_transition(
    blockchain_test: BlockchainTestFiller,
    pre: Alloc,
    fork: Fork,
) -> None:
    """
    The ``SSTORE`` first-change cost is repriced across the Amsterdam
    boundary, and EIP-8038 changes the *model*, not a single number.

    Before the fork (parent schedule) a zero-to-nonzero ``SSTORE`` is a
    flat execution charge (``COLD_STORAGE_ACCESS + STORAGE_SET``) with no
    state-gas dimension. After the fork the charge splits: the execution
    portion drops to ``COLD_STORAGE_ACCESS + STORAGE_WRITE`` while the
    bulk moves into the new state-gas dimension, and the clear refund
    rises. Every magnitude is derived from the two schedules; nothing is
    hardcoded.

    The transition is asserted at the derived-constant level (the
    runtime opcode cost cannot isolate the execution portion without the
    state-gas confounder) and a zero-to-nonzero ``SSTORE`` is exercised
    in both blocks to prove it still sets the slot in each regime.
    """
    before = fork.fork_at(timestamp=BEFORE_TS)
    after = fork.fork_at(timestamp=AFTER_TS)

    # First-change (zero -> nonzero, cold) SSTORE in each regime.
    sstore = Op.SSTORE(new_value=1)

    execution_before = sstore.execution_cost(before)
    execution_after = sstore.execution_cost(after)
    state_before = sstore.state_cost(before)
    state_after = sstore.state_cost(after)
    total_before = sstore.gas_cost(before)
    total_after = sstore.gas_cost(after)

    # The repricing changes the execution charge, introduces the state
    # dimension, and therefore moves the total.
    assert execution_after != execution_before
    assert state_before == 0
    assert state_after > 0
    assert total_after != total_before

    # The storage-clear refund also rises across the boundary.
    clear_sstore = Op.SSTORE.with_metadata(
        original_value=1, current_value=1, new_value=0
    )
    refund_before = clear_sstore.refund(before)
    refund_after = clear_sstore.refund(after)
    assert refund_after > refund_before

    # Exercise the zero-to-nonzero SSTORE in both regimes; the slot ends
    # set in each block.
    storage_before = Storage()
    contract_before = pre.deploy_contract(
        code=Op.SSTORE(storage_before.store_next(1), 1),
    )
    storage_after = Storage()
    contract_after = pre.deploy_contract(
        code=Op.SSTORE(storage_after.store_next(1), 1),
    )

    blocks = transition_blocks(contract_before, contract_after, pre)

    post = {
        contract_before: Account(storage=storage_before),
        contract_after: Account(storage=storage_after),
    }
    blockchain_test(pre=pre, blocks=blocks, post=post)

Parametrized Test Cases

This test generates 1 parametrized test case across 1 fork.