Skip to content

test_sload_warmth_reverts_on_subcall_revert()

Documentation for tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sload_gas.py::test_sload_warmth_reverts_on_subcall_revert@2867859a.

Generate fixtures for these test cases for Amsterdam with:

fill -v tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sload_gas.py::test_sload_warmth_reverts_on_subcall_revert --fork Amsterdam

Warmth acquired inside a reverted sub-call does not persist.

An inner contract SLOADs the slot via DELEGATECALL (so the warmed (address, slot) pair belongs to the outer account) then REVERTs. Back in the outer frame, that same slot's first SLOAD is cold again and is charged COLD_STORAGE_ACCESS, proving the warm-slot set is rolled back on revert.

Source code in tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sload_gas.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement()
def test_sload_warmth_reverts_on_subcall_revert(
    state_test: StateTestFiller,
    env: Environment,
    pre: Alloc,
    fork: Fork,
) -> None:
    """
    Warmth acquired inside a reverted sub-call does not persist.

    An inner contract ``SLOAD``s the slot via ``DELEGATECALL`` (so the
    warmed ``(address, slot)`` pair belongs to the outer account) then
    ``REVERT``s. Back in the outer frame, that same slot's first
    ``SLOAD`` is cold again and is charged ``COLD_STORAGE_ACCESS``,
    proving the warm-slot set is rolled back on revert.
    """
    slot = 0x42
    cold_gas = Op.SLOAD(key_warm=False).gas_cost(fork)

    # Inner: read the slot (warming it in the delegating account's
    # context) then revert.
    inner = pre.deploy_contract(
        code=Op.SLOAD(slot) + Op.REVERT(0, 0),
    )

    # Outer: DELEGATECALL inner (which reverts), then measure its own
    # first SLOAD of the slot. DELEGATECALL keeps the outer account's
    # storage context, so inner's read warms (outer, slot); the revert
    # discards that warmth, making the measured read cold.
    measured_code = Op.SLOAD(slot)
    overhead_cost = measured_code.gas_cost(fork) - Op.SLOAD(
        key_warm=False
    ).gas_cost(fork)

    outer_code: Bytecode = Op.POP(
        Op.DELEGATECALL(gas=100_000, address=inner)
    ) + CodeGasMeasure(
        code=measured_code,
        overhead_cost=overhead_cost,
        extra_stack_items=1,
    )
    outer = pre.deploy_contract(code=outer_code, storage={slot: 1})

    tx = Transaction(to=outer, sender=pre.fund_eoa())

    # Slot 0 holds the measured (cold) read; the read slot keeps its
    # value.
    post = {outer: Account(storage={0: cold_gas, slot: 1})}
    state_test(env=env, pre=pre, post=post, tx=tx)

Parametrized Test Cases

This test generates 1 parametrized test case across 1 fork.