Skip to content

test_selfdestruct_existing()

Documentation for tests/benchmark/compute/instruction/test_system.py::test_selfdestruct_existing@ffe48572.

Generate fixtures for these test cases for Bogota with:

fill -v tests/benchmark/compute/instruction/test_system.py::test_selfdestruct_existing --gas-benchmark-values 1

Benchmark SELFDESTRUCT instruction for existing contracts.

Source code in tests/benchmark/compute/instruction/test_system.py
@pytest.mark.parametrize("value_bearing", [True, False])
def test_selfdestruct_existing(
    benchmark_test: BenchmarkTestFiller,
    pre: Alloc,
    value_bearing: bool,
    fork: Fork,
    gas_benchmark_value: int,
) -> None:
    """Benchmark SELFDESTRUCT instruction for existing contracts."""
    selfdestructable_contract = Op.SELFDESTRUCT(Op.CALLER, address_warm=True)

    # Initcode
    initcode = (
        Op.MSTORE8(
            0,
            Op.CALLER.int(),
            # gas accounting
            old_memory_size=0,
            new_memory_size=2,
        )
        + Op.MSTORE8(1, Op.SELFDESTRUCT.int())
        + Op.RETURN(0, 2, code_deposit_size=2)
    )

    # Factory Contract Setup
    # CALLDATA[0:32] = start index
    # CALLDATA[32:64] = end index
    factory_setup = (
        Op.MSTORE(
            0,
            initcode.hex(),
            # Gas accounting
            old_memory_size=0,
            new_memory_size=32,
        )
        + Op.ADD(1, Op.CALLDATALOAD(32))
        + Op.CALLDATALOAD(0)
    )

    factory_iterating = While(
        body=Op.POP(
            Op.CREATE2(
                value=1 if value_bearing else 0,
                offset=32 - len(initcode),
                size=len(initcode),
                salt=Op.DUP1,
                # gas accounting
                init_code_size=len(initcode),
            )
        ),
        condition=Op.PUSH1(1) + Op.ADD + Op.DUP1 + Op.DUP3 + Op.GT,
    )

    factory_code = IteratingBytecode(
        setup=factory_setup,
        iterating=factory_iterating,
        iterating_subcall=initcode,
        cleanup=Op.STOP,
    )

    factory_address = pre.deploy_contract(
        code=factory_code,
        balance=10**18,
    )

    create2_preimage = Create2PreimageLayout(
        factory_address=factory_address,
        salt=Op.CALLDATALOAD(0),
        init_code_hash=initcode.keccak256(),
    )

    # Attack Contract Setup
    # CALLDATA[0:32] = start index
    # CALLDATA[32:64] = end index
    attack_setup = (
        create2_preimage + Op.ADD(1, Op.CALLDATALOAD(32)) + Op.CALLDATALOAD(0)
    )

    loop = While(
        body=Op.POP(
            Op.CALL(
                address=create2_preimage.address_op(),
                address_warm=False,
            )
        )
        + create2_preimage.increment_salt_op(),
        condition=Op.PUSH1(1) + Op.ADD + Op.DUP1 + Op.DUP3 + Op.GT,
    )

    attack_code = IteratingBytecode(
        setup=attack_setup,
        iterating=loop,
        iterating_subcall=selfdestructable_contract,
        cleanup=Op.STOP,
    )

    attack_code_address = pre.deploy_contract(code=attack_code)

    def calldata(iteration_count: int, start_iteration: int) -> bytes:
        index_end = iteration_count + start_iteration - 1
        return Hash(start_iteration) + Hash(index_end)

    # Compute iteration counts and expected gas from the gas model.
    iteration_counts = list(
        attack_code.tx_iterations_by_gas_limit(
            fork=fork,
            gas_limit=gas_benchmark_value,
            calldata=calldata,
        )
    )
    num_contracts = sum(iteration_counts)

    def factory_calldata(iteration_count: int, start_iteration: int) -> bytes:
        index_end = iteration_count + start_iteration - 1
        return Hash(start_iteration) + Hash(index_end)

    with TestPhaseManager.setup():
        setup_sender = pre.fund_eoa()
        setup_txs = list(
            factory_code.transactions_by_total_iteration_count(
                fork=fork,
                total_iterations=num_contracts,
                sender=setup_sender,
                to=factory_address,
                calldata=factory_calldata,
            )
        )

    with TestPhaseManager.execution():
        attack_sender = pre.fund_eoa()
        exec_txs = list(
            attack_code.transactions_by_gas_limit(
                fork=fork,
                gas_limit=gas_benchmark_value,
                sender=attack_sender,
                to=attack_code_address,
                calldata=calldata,
            )
        )

    total_gas_cost = sum(tx.gas_cost for tx in exec_txs)

    post = {}
    for i in range(num_contracts):
        deployed_contract_address = compute_create2_address(
            address=factory_address,
            salt=i,
            initcode=initcode,
        )
        post[deployed_contract_address] = Account(nonce=1)

    post[attack_code_address] = Account(
        balance=num_contracts if value_bearing else 0
    )

    benchmark_test(
        post=post,
        target_opcode=Op.SELFDESTRUCT,
        blocks=[
            Block(txs=setup_txs),
            Block(txs=exec_txs),
        ],
        expected_benchmark_gas_used=total_gas_cost,
    )

Parametrized Test Cases

This test generates 2 parametrized test cases across 4 forks.