Skip to content

test_set_delegation_oog_charge_point()

Documentation for tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_oog.py::test_set_delegation_oog_charge_point@26332146.

Generate fixtures for these test cases for Amsterdam with:

fill -v tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_oog.py::test_set_delegation_oog_charge_point --fork Amsterdam

OOG at each distinct charge point inside set_delegation rolls the whole authorization phase back atomically.

The first authorization creates and delegates its authority in full (exercising NEW_ACCOUNT + ACCOUNT_WRITE + AUTH_BASE). The gas_limit then starves the second authorization at exactly the parametrized charge:

  • new_account: the second (a creation) runs out at its opening NEW_ACCOUNT state charge.
  • account_write: the second covers NEW_ACCOUNT but runs out at the following ACCOUNT_WRITE execution charge.
  • auth_base: the second (a delegation on an existing empty EOA) covers its first-write ACCOUNT_WRITE but runs out at the following AUTH_BASE state charge.
  • succeeds: as auth_base, but with the one starved gas restored the closing AUTH_BASE is covered exactly and both authorizations apply, pinning the off-by-one boundary of the last charge from above.

In every out-of-gas case the transaction halts in set_delegation and both authorizations are rolled back -- the first, already applied, as well as the second -- so both authorities return to their pre-tx state. The receipt shows the full gas_limit consumed (exactly covered, in the succeeds case) and the sender nonce is not rolled back.

Both authorities are read during authorization validation before the halt, so per EIP-7928 they still appear in the block access list; only in the succeeds case do they record changes. The recipient is only loaded by the top-frame dispatch, so it must be absent whenever the halt precedes it.

Source code in tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_oog.py
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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
@pytest.mark.parametrize(
    "outcome", ["new_account", "account_write", "auth_base", "succeeds"]
)
def test_set_delegation_oog_charge_point(
    fork: Fork,
    pre: Alloc,
    state_test: StateTestFiller,
    outcome: str,
) -> None:
    """
    OOG at each distinct charge point inside ``set_delegation`` rolls
    the whole authorization phase back atomically.

    The first authorization creates and delegates its authority in full
    (exercising ``NEW_ACCOUNT`` + ``ACCOUNT_WRITE`` + ``AUTH_BASE``). The
    ``gas_limit`` then starves the second authorization at exactly the
    parametrized charge:

    - ``new_account``: the second (a creation) runs out at its opening
      ``NEW_ACCOUNT`` state charge.
    - ``account_write``: the second covers ``NEW_ACCOUNT`` but runs out
      at the following ``ACCOUNT_WRITE`` execution charge.
    - ``auth_base``: the second (a delegation on an existing empty EOA)
      covers its first-write ``ACCOUNT_WRITE`` but runs out at the
      following ``AUTH_BASE`` state charge.
    - ``succeeds``: as ``auth_base``, but with the one starved gas
      restored the closing ``AUTH_BASE`` is covered exactly and both
      authorizations apply, pinning the off-by-one boundary of the
      last charge from above.

    In every out-of-gas case the transaction halts in
    ``set_delegation`` and both authorizations are rolled back -- the
    first, already applied, as well as the second -- so both
    authorities return to their pre-tx state. The receipt shows the
    full ``gas_limit`` consumed (exactly covered, in the ``succeeds``
    case) and the sender nonce is not rolled back.

    Both authorities are read during authorization validation before
    the halt, so per EIP-7928 they still appear in the block access
    list; only in the ``succeeds`` case do they record changes. The
    recipient is only loaded by the top-frame dispatch, so it must be
    absent whenever the halt precedes it.
    """
    gas_costs = fork.gas_costs()
    sender = pre.fund_eoa()
    recipient = pre.deploy_contract(code=Op.STOP)

    first = build_authorization(pre, AuthorizationAction.CREATES_ACCOUNT)
    if outcome in ("auth_base", "succeeds"):
        second = build_authorization(
            pre, AuthorizationAction.SETS_NEW_DELEGATION
        )
    else:
        second = build_authorization(pre, AuthorizationAction.CREATES_ACCOUNT)

    authorization_list = [first.authorization, second.authorization]

    intrinsic_execution = _intrinsic_execution(
        fork, authorization_list, recipient_type=RecipientType.CONTRACT
    )
    first_auth_charges = _auth_top_frame_charges(fork, [first.authorization])

    # gas_left entering set_delegation is gas_limit - intrinsic_execution
    # (the state reservoir is zero). The first authorization is applied
    # in full; the second is starved by one gas at the target charge,
    # after covering any charges that precede it within that same
    # authorization -- or, for ``succeeds``, covered exactly.
    if outcome == "new_account":
        preceding = 0
        shortfall_charge = gas_costs.NEW_ACCOUNT
    elif outcome == "account_write":
        preceding = gas_costs.NEW_ACCOUNT
        shortfall_charge = gas_costs.ACCOUNT_WRITE
    else:  # auth_base / succeeds
        preceding = gas_costs.ACCOUNT_WRITE
        shortfall_charge = gas_costs.AUTH_BASE

    gas_limit = (
        intrinsic_execution + first_auth_charges + preceding + shortfall_charge
    )
    if outcome != "succeeds":
        gas_limit -= 1

    tx = Transaction(
        sender=sender,
        to=recipient,
        value=0,
        authorization_list=authorization_list,
        gas_limit=gas_limit,
        expected_receipt=TransactionReceipt(
            cumulative_gas_used=gas_limit,
        ),
    )

    post: dict[EOA, Account | None]
    if outcome == "succeeds":
        post = {
            first.authority: first.applied_account,
            second.authority: second.applied_account,
        }
        expected_block_access_list = BlockAccessListExpectation(
            account_expectations={
                recipient: BalAccountExpectation.empty(),
                first.authority: _applied_delegation_bal(first),
                second.authority: _applied_delegation_bal(second),
            }
        )
    else:
        post = {
            first.authority: first.original_account,
            second.authority: second.original_account,
        }
        # An implementation recording accesses only for dispatched
        # frames would drop the authority entries; one recording the
        # recipient at inclusion would add it. Either forks on the BAL
        # hash.
        expected_block_access_list = BlockAccessListExpectation(
            account_expectations={
                recipient: None,
                first.authority: BalAccountExpectation.empty(),
                second.authority: BalAccountExpectation.empty(),
            }
        )

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

Parametrized Test Cases

This test generates 4 parametrized test cases across 1 fork.