Skip to content

test_value_move_to_precompiles()

Documentation for tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py::test_value_move_to_precompiles@a9abd46e.

Generate fixtures for these test cases for Amsterdam with:

fill -v tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py::test_value_move_to_precompiles --fork Amsterdam

Ensure value moving transactions to precompiles charge gas correctly.

Precompile recipients pay the same COLD_ACCOUNT_ACCESS at intrinsic time as any other non-self target -- access lists do not warm transaction-level accounts. A value transfer to a precompile additionally pays the transfer-log and value-transfer charges.

The top-frame NEW_ACCOUNT state charge keys solely on EIP-161 emptiness; a precompile address is not special-cased. The pre_funded parameter exercises both pre-tx states:

  • not_funded: the precompile address is empty per EIP-161, so a value transfer creates it and pays NEW_ACCOUNT -- exactly like any other empty recipient.
  • pre_funded: the precompile already holds a balance and is therefore alive, so no NEW_ACCOUNT charge applies.
Source code in tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
@pytest.mark.parametrize(
    "value",
    [
        pytest.param(0, id="zero_value"),
        pytest.param(1, id="non-zero_value"),
    ],
)
@pytest.mark.parametrize(
    "pre_funded",
    [
        pytest.param(True, id="pre_funded"),
        pytest.param(False, id="not_funded"),
    ],
)
@pytest.mark.with_all_precompiles
def test_value_move_to_precompiles(
    fork: Fork,
    pre: Alloc,
    state_test: StateTestFiller,
    precompile: Address,
    pre_funded: bool,
    value: int,
) -> None:
    """
    Ensure value moving transactions to precompiles charge gas correctly.

    Precompile recipients pay the same ``COLD_ACCOUNT_ACCESS`` at
    intrinsic time as any other non-self target -- access lists do
    not warm transaction-level accounts. A value transfer to a
    precompile additionally pays the transfer-log and value-transfer
    charges.

    The top-frame ``NEW_ACCOUNT`` state charge keys solely on EIP-161
    emptiness; a precompile address is not special-cased. The
    ``pre_funded`` parameter exercises both pre-tx states:

    - ``not_funded``: the precompile address is empty per EIP-161, so a
      value transfer creates it and pays ``NEW_ACCOUNT`` -- exactly
      like any other empty recipient.
    - ``pre_funded``: the precompile already holds a balance and is
      therefore alive, so no ``NEW_ACCOUNT`` charge applies.
    """
    sender_initial_balance = 10**18
    sender = pre.fund_eoa(sender_initial_balance)

    pre_funded_amount = 0
    if pre_funded:
        pre_funded_amount = 1
        pre.fund_address(precompile, amount=pre_funded_amount)

    tx_data = _precompile_calldata(precompile)

    intrinsic_gas = fork.transaction_intrinsic_cost_calculator()(
        calldata=tx_data,
        sends_value=bool(value),
        recipient_type=RecipientType.PRECOMPILE,
        return_cost_deducted_prior_execution=True,
    )
    # A value transfer to an empty (not pre-funded) precompile fires the
    # top-frame ``NEW_ACCOUNT`` state charge, modelled via
    # ``EMPTY_ACCOUNT``; a pre-funded precompile is alive and exempt.
    state_recipient_type = (
        RecipientType.PRECOMPILE if pre_funded else RecipientType.EMPTY_ACCOUNT
    )
    top_frame_state_gas = fork.transaction_top_frame_state_gas(
        sends_value=bool(value),
        recipient_type=state_recipient_type,
    )

    if value > 0:
        expected_logs = [transfer_log(sender, precompile, value)]
    else:
        expected_logs = []

    gas_price = 1_000_000_000

    tx = Transaction(
        sender=sender,
        to=precompile,
        value=value,
        data=tx_data,
        gas_price=gas_price,
        expected_receipt=TransactionReceipt(logs=expected_logs),
    )

    # Exact sender balance is generally not checked because precompile
    # execution gas varies across the matrix. For identity with empty
    # calldata, the execution gas is deterministic, so pin the exact
    # balance to make the empty-precompile ``NEW_ACCOUNT`` charge a
    # source-level assertion.
    final_precompile_balance = pre_funded_amount + value
    expected_precompile: Account | None
    if final_precompile_balance > 0:
        expected_precompile = Account(balance=final_precompile_balance)
    else:
        expected_precompile = None
    expected_sender = Account(nonce=1)
    if precompile == Address(0x04):
        gas_costs = fork.gas_costs()
        precompile_execution_gas = (
            gas_costs.PRECOMPILE_IDENTITY_BASE
            + gas_costs.PRECOMPILE_IDENTITY_PER_WORD
            * ((len(tx_data) + 31) // 32)
        )
        total_gas_cost = (
            intrinsic_gas + top_frame_state_gas + precompile_execution_gas
        )
        expected_sender = Account(
            nonce=1,
            balance=(
                sender_initial_balance - value - total_gas_cost * gas_price
            ),
        )
    post = {
        sender: expected_sender,
        precompile: expected_precompile,
    }

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

Parametrized Test Cases

This test generates 72 parametrized test cases across 1 fork.