Skip to content

test_delegatecall_child_spill_not_double_charged()

Documentation for tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py::test_delegatecall_child_spill_not_double_charged@622620d8.

Generate fixtures for these test cases for Amsterdam with:

fill -v tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py::test_delegatecall_child_spill_not_double_charged --fork Amsterdam

Test DELEGATECALL child state gas paid from gas_left is not recharged.

With the gas limit pinned to the Amsterdam tx gas cap and no requested reservoir (state_gas_reservoir=0), the top-level frame starts with no state gas reservoir and the child pays for SSTOREs by spilling from gas_left. The parent frame must not charge the same state growth again at frame end: the header bills the storage sets once, in the state dimension, so a second charge surfaces as inflated gas_used.

mixed funds one set from the reservoir and spills the rest, so the header must stay put however the charge is split.

Source code in tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py
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
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
@pytest.mark.parametrize("funding", ["spill", "mixed"])
@pytest.mark.valid_from("EIP8037")
def test_delegatecall_child_spill_not_double_charged(
    state_test: StateTestFiller,
    pre: Alloc,
    fork: Fork,
    funding: str,
) -> None:
    """
    Test DELEGATECALL child state gas paid from `gas_left` is not recharged.

    With the gas limit pinned to the Amsterdam tx gas cap and no requested
    reservoir (`state_gas_reservoir=0`), the top-level frame starts with no
    state gas reservoir and the child pays for SSTOREs by spilling from
    `gas_left`. The parent frame must not charge the same state growth again
    at frame end: the header bills the storage sets once, in the state
    dimension, so a second charge surfaces as inflated `gas_used`.

    `mixed` funds one set from the reservoir and spills the rest, so the
    header must stay put however the charge is split.
    """
    num_sstores = 6
    child_code = (
        sum(
            Op.SSTORE(
                i,
                1,
                # gas accounting
                original_value=0,
                new_value=1,
            )
            for i in range(num_sstores)
        )
        + Op.STOP
    )

    child = pre.deploy_contract(code=child_code)

    caller_code = Op.POP(
        Op.DELEGATECALL(
            gas=Op.GAS,
            address=child,
            args_offset=0,
            args_size=0,
            ret_offset=0,
            ret_size=0,
        )
    )
    caller = pre.deploy_contract(code=caller_code)

    state_gas = child_code.state_cost(fork)
    execution_gas = (
        fork.transaction_intrinsic_cost_calculator()()
        + caller_code.execution_cost(fork)
        + child_code.execution_cost(fork)
    )
    expected_gas_used = max(execution_gas, state_gas)
    assert expected_gas_used == state_gas, (
        "expected state gas to dominate execution gas"
    )

    if funding == "mixed":
        reservoir = Op.SSTORE(new_value=1).state_cost(fork)
    else:
        reservoir = 0

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

    post = {
        caller: Account(storage=dict.fromkeys(range(num_sstores), 1)),
    }
    state_test(
        pre=pre,
        post=post,
        tx=tx,
        blockchain_test_header_verify=Header(gas_used=expected_gas_used),
    )

Parametrized Test Cases

This test generates 2 parametrized test cases across 1 fork.