Skip to content

test_sstore_cold_then_warm_same_slot()

Documentation for tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_gas.py::test_sstore_cold_then_warm_same_slot@343274cc.

Generate fixtures for these test cases for Amsterdam with:

fill -v tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_gas.py::test_sstore_cold_then_warm_same_slot --fork Amsterdam

A first SSTORE on a cold slot warms it; the second in-frame SSTORE of the same slot is charged only WARM_SLOAD.

The slot starts non-zero (original 1) and is left unlisted, so the first write is cold and is its first change (original == current != new), costing COLD_STORAGE_ACCESS + STORAGE_WRITE. That write warms the slot, so the second write -- which moves the slot again without being a first change -- costs only WARM_SLOAD, with no further STORAGE_WRITE. Slot 0 records the cold first write and slot 1 the warm second write; the data slot keeps its final value.

Source code in tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_gas.py
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement()
def test_sstore_cold_then_warm_same_slot(
    state_test: StateTestFiller,
    pre: Alloc,
    fork: Fork,
) -> None:
    """
    A first ``SSTORE`` on a cold slot warms it; the second in-frame
    ``SSTORE`` of the same slot is charged only ``WARM_SLOAD``.

    The slot starts non-zero (original 1) and is left unlisted, so the
    first write is cold and is its first change (original == current !=
    new), costing ``COLD_STORAGE_ACCESS + STORAGE_WRITE``.
    That write warms the slot, so the second write -- which moves the slot
    again without being a first change -- costs only ``WARM_SLOAD``,
    with no further ``STORAGE_WRITE``. Slot 0 records the cold first write
    and slot 1 the warm second write; the data slot keeps its final value.
    """
    data_slot = 0x42

    # First write: cold, first change of a non-zero-original slot. The
    # bare (operand-free) opcode carries the same metadata so that the
    # CodeGasMeasure overhead resolves to just the two operand PUSHes.
    first_bare = Op.SSTORE.with_metadata(
        key_warm=False,
        original_value=1,
        current_value=1,
        new_value=2,
    )
    first = first_bare(data_slot, 2)
    # Second write: same slot, now warm; not a first change, so the
    # write cost is not re-charged and only the warm access applies.
    second_bare = Op.SSTORE.with_metadata(
        key_warm=True,
        original_value=1,
        current_value=2,
        new_value=3,
    )
    second = second_bare(data_slot, 3)

    expected_first = first_bare.execution_cost(fork)
    expected_second = second_bare.execution_cost(fork)

    # Each measured write stores its own runtime cost; the overhead
    # subtraction strips the two operand PUSHes so the stored value is the
    # bare SSTORE cost. The second write finds the slot warm.
    code = CodeGasMeasure(
        code=first,
        overhead_cost=first.gas_cost(fork) - first_bare.gas_cost(fork),
        extra_stack_items=0,
        sstore_key=0,
    ) + CodeGasMeasure(
        code=second,
        overhead_cost=second.gas_cost(fork) - second_bare.gas_cost(fork),
        extra_stack_items=0,
        sstore_key=1,
    )

    contract = pre.deploy_contract(code=code, storage={data_slot: 1})

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

    # Slots 0/1 hold the two measured writes; the data slot ends at its
    # final written value.
    post = {
        contract: Account(
            storage={0: expected_first, 1: expected_second, data_slot: 3}
        )
    }
    state_test(pre=pre, post=post, tx=tx)

Parametrized Test Cases

This test generates 1 parametrized test case across 1 fork.