Skip to content

test_bal_create_and_oog()

Documentation for tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py::test_bal_create_and_oog@87aba1a3.

Generate fixtures for these test cases for Amsterdam with:

fill -v tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py::test_bal_create_and_oog --fork Amsterdam

CREATE/CREATE2 OOG boundary test at three gas levels.

OOG_BEFORE_TARGET_ACCESS and OOG_AFTER_TARGET_ACCESS differ by exactly 1 gas, proving the static cost boundary: below it the created address is NOT in BAL, at it the address IS in BAL.

Source code in tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
@pytest.mark.parametrize(
    "oog_boundary",
    [
        OutOfGasBoundary.OOG_BEFORE_TARGET_ACCESS,
        OutOfGasBoundary.OOG_AFTER_TARGET_ACCESS,
        OutOfGasBoundary.SUCCESS,
    ],
    ids=lambda x: x.value,
)
@pytest.mark.with_all_create_opcodes
def test_bal_create_and_oog(
    pre: Alloc,
    blockchain_test: BlockchainTestFiller,
    fork: Fork,
    create_opcode: Op,
    oog_boundary: OutOfGasBoundary,
) -> None:
    """
    CREATE/CREATE2 OOG boundary test at three gas levels.

    OOG_BEFORE_TARGET_ACCESS and OOG_AFTER_TARGET_ACCESS differ by
    exactly 1 gas, proving the static cost boundary: below it the
    created address is NOT in BAL, at it the address IS in BAL.
    """
    alice = pre.fund_eoa()

    init_code = Initcode(deploy_code=Op.STOP)
    init_code_bytes = bytes(init_code)

    factory_mstore = Op.MSTORE(
        0, Op.PUSH32(init_code_bytes), new_memory_size=32
    )
    factory_create = create_opcode(
        value=0,
        offset=32 - len(init_code_bytes),
        size=len(init_code_bytes),
        init_code_size=len(init_code_bytes),
        account_new=False,
    )
    factory_sstore = Op.SSTORE(0x00, 1)
    oog_sink_memory_size = 10000 * 32
    factory_oog_sink = Op.MSTORE(
        oog_sink_memory_size - 32,
        0,
        old_memory_size=32,
        new_memory_size=oog_sink_memory_size,
    )
    factory_code = (
        factory_mstore + factory_create + factory_oog_sink + factory_sstore
    )

    factory = pre.deploy_contract(
        code=factory_code,
        storage={0x00: 0xDEAD},
    )

    created_address = compute_create_address(
        address=factory,
        nonce=1,
        salt=0,
        initcode=init_code_bytes,
        opcode=create_opcode,
    )
    # Pre-fund the address so no new account is created
    pre.fund_address(created_address, 1)

    intrinsic_cost = fork.transaction_intrinsic_cost_calculator()()
    create_static_cost = factory_mstore.gas_cost(
        fork
    ) + factory_create.gas_cost(fork)

    if oog_boundary == OutOfGasBoundary.OOG_BEFORE_TARGET_ACCESS:
        # 1 gas short of CREATE static cost — no state access
        gas_limit = intrinsic_cost + create_static_cost - 1
    elif oog_boundary == OutOfGasBoundary.OOG_AFTER_TARGET_ACCESS:
        # Exactly the CREATE static cost — address accessed, child
        # frame gets 0 gas, CREATE fails, sink forces OOG after access
        gas_limit = intrinsic_cost + create_static_cost
    else:
        # Full success: static cost + child frame (63/64 rule) +
        # SSTORE + gas sink.
        child_gas = init_code.gas_cost(fork)
        remaining_needed = (child_gas * 64 + 62) // 63
        gas_limit = (
            intrinsic_cost
            + create_static_cost
            + remaining_needed
            + factory_sstore.gas_cost(fork)
            + factory_oog_sink.gas_cost(fork)
        )

    tx = Transaction(
        sender=alice,
        to=factory,
        gas_limit=gas_limit,
    )

    account_expectations: Dict[Address, BalAccountExpectation | None]
    post: Dict[Address, Account | None]

    if oog_boundary == OutOfGasBoundary.OOG_BEFORE_TARGET_ACCESS:
        # Created address NOT in BAL — static check failed before access
        account_expectations = {
            alice: BalAccountExpectation(
                nonce_changes=[
                    BalNonceChange(block_access_index=1, post_nonce=1)
                ],
            ),
            factory: BalAccountExpectation.empty(),
            created_address: None,
        }
        post = {
            alice: Account(nonce=1),
            factory: Account(nonce=1, storage={0x00: 0xDEAD}),
            created_address: Account(balance=1, code=b"", nonce=0),
        }
    elif oog_boundary == OutOfGasBoundary.OOG_AFTER_TARGET_ACCESS:
        # Created address IS in BAL (accessed during collision check),
        # but tx OOGs so all state changes revert — only access is
        # recorded, no nonce/code/storage changes.
        account_expectations = {
            alice: BalAccountExpectation(
                nonce_changes=[
                    BalNonceChange(block_access_index=1, post_nonce=1)
                ],
            ),
            factory: BalAccountExpectation.empty(),
            created_address: BalAccountExpectation.empty(),
        }
        post = {
            alice: Account(nonce=1),
            factory: Account(nonce=1, storage={0x00: 0xDEAD}),
            created_address: Account(balance=1, code=b"", nonce=0),
        }
    else:
        # SUCCESS: created address in BAL with nonce and code changes
        account_expectations = {
            alice: BalAccountExpectation(
                nonce_changes=[
                    BalNonceChange(block_access_index=1, post_nonce=1)
                ],
            ),
            factory: BalAccountExpectation(
                nonce_changes=[
                    BalNonceChange(block_access_index=1, post_nonce=2)
                ],
                storage_changes=[
                    BalStorageSlot(
                        slot=0x00,
                        slot_changes=[
                            BalStorageChange(
                                block_access_index=1, post_value=1
                            )
                        ],
                    )
                ],
            ),
            created_address: BalAccountExpectation(
                nonce_changes=[
                    BalNonceChange(block_access_index=1, post_nonce=1)
                ],
                code_changes=[
                    BalCodeChange(
                        block_access_index=1,
                        new_code=bytes(Op.STOP),
                    )
                ],
            ),
        }
        post = {
            alice: Account(nonce=1),
            factory: Account(nonce=2, storage={0x00: 1}),
            created_address: Account(balance=1, code=Op.STOP, nonce=1),
        }

    blockchain_test(
        pre=pre,
        blocks=[
            Block(
                txs=[tx],
                expected_block_access_list=BlockAccessListExpectation(
                    account_expectations=account_expectations
                ),
            )
        ],
        post=post,
    )

Parametrized Test Cases

This test generates 6 parametrized test cases across 1 fork.