Skip to content

test_sstore_oog_reservoir_inflation_detection()

Documentation for tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_ordering.py::test_sstore_oog_reservoir_inflation_detection@87aba1a3.

Generate fixtures for these test cases for Amsterdam with:

fill -v tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_ordering.py::test_sstore_oog_reservoir_inflation_detection --fork Amsterdam

Detect SSTORE state gas ordering via reservoir inflation.

A factory does CREATE + SSTORE where SSTORE OOGs (1 gas short). After factory failure, the parent's reservoir should contain only CREATE's state gas. A probe contract tests this by doing 4 SSTOREs that need more total state gas than the correct reservoir but less than the inflated one.

With correct ordering (regular gas first): probe OOGs on 4th SSTORE. With wrong ordering (state gas first): reservoir is inflated, probe succeeds.

Source code in tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_ordering.py
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
@pytest.mark.valid_from("EIP8037")
def test_sstore_oog_reservoir_inflation_detection(
    state_test: StateTestFiller,
    pre: Alloc,
    fork: Fork,
) -> None:
    """
    Detect SSTORE state gas ordering via reservoir inflation.

    A factory does CREATE + SSTORE where SSTORE OOGs (1 gas short).
    After factory failure, the parent's reservoir should contain only
    CREATE's state gas. A probe contract tests this by doing 4 SSTOREs
    that need more total state gas than the correct reservoir but less
    than the inflated one.

    With correct ordering (regular gas first): probe OOGs on 4th SSTORE.
    With wrong ordering (state gas first): reservoir is inflated,
    probe succeeds.
    """
    gas_costs = fork.gas_costs()
    initcode = Initcode(deploy_code=Op.STOP)
    initcode_len = len(initcode)

    factory_code = Op.CALLDATACOPY(
        0,
        0,
        Op.CALLDATASIZE,
        data_size=initcode_len,
        new_memory_size=initcode_len,
    ) + Op.SSTORE(
        0,
        Op.CREATE(
            value=0,
            offset=0,
            size=Op.CALLDATASIZE,
            init_code_size=initcode_len,
        ),
    )
    factory = pre.deploy_contract(factory_code)

    factory_gas = (
        factory_code.gas_cost(fork)
        + initcode.execution_gas(fork)
        + initcode.deployment_gas(fork)
    )

    # Probe: 4 SSTOREs to cold slots. Total state gas exceeds the
    # correct reservoir (CREATE state gas only) but fits within the
    # inflated reservoir (CREATE + SSTORE state gas).
    probe = pre.deploy_contract(
        Op.SSTORE(0, 1) + Op.SSTORE(1, 1) + Op.SSTORE(2, 1) + Op.SSTORE(3, 1)
    )

    # Compute probe gas: enough for 4 SSTOREs' regular gas + pushes,
    # but after 4th regular charge, gas_left < the state gas spill.
    sstore_regular = gas_costs.COLD_STORAGE_WRITE
    sstore_state = Op.SSTORE(new_value=1).state_cost(fork)
    push_per_sstore = 2 * gas_costs.VERY_LOW
    create_state_gas = fork.create_state_gas(
        code_size=len(initcode.deploy_code)
    )
    spill = 4 * sstore_state - create_state_gas
    probe_gas = 4 * (push_per_sstore + sstore_regular) + spill // 2

    caller_storage = Storage()
    caller = pre.deploy_contract(
        Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE)
        + Op.POP(
            Op.CALL(
                gas=factory_gas - 1,
                address=factory,
                value=0,
                args_offset=0,
                args_size=Op.CALLDATASIZE,
                ret_offset=0,
                ret_size=0,
            )
        )
        + Op.SSTORE(
            caller_storage.store_next(0, "probe_must_fail"),
            Op.CALL(gas=probe_gas, address=probe),
        )
    )

    sender = pre.fund_eoa()
    tx = Transaction(
        sender=sender,
        to=caller,
        data=bytes(initcode),
        state_gas_reservoir=0,
    )

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

    state_test(pre=pre, tx=tx, post=post)

Parametrized Test Cases

This test generates 1 parametrized test case across 1 fork.