Skip to content

test_selfdestruct_initcode()

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

Generate fixtures for these test cases for Bogota with:

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

Benchmark SELFDESTRUCT instruction executed in initcode.

Source code in tests/benchmark/compute/instruction/test_system.py
@pytest.mark.parametrize(
    "value_bearing,beneficiary_is_self",
    [
        pytest.param(False, False, id="without value"),
        pytest.param(True, False, id="with value moved to the creator"),
        pytest.param(True, True, id="with value burnt to self"),
    ],
)
def test_selfdestruct_initcode(
    benchmark_test: BenchmarkTestFiller,
    pre: Alloc,
    value_bearing: bool,
    beneficiary_is_self: bool,
    fork: Fork,
    gas_benchmark_value: int,
) -> None:
    """Benchmark SELFDESTRUCT instruction executed in initcode."""
    beneficiary = Op.ADDRESS if beneficiary_is_self else Op.CALLER
    initcode = Op.SELFDESTRUCT(beneficiary, address_warm=True)

    # CALLDATA[0:32] = iteration_count
    setup = (
        Op.MSTORE(
            0,
            initcode.hex(),
            # Gas accounting
            old_memory_size=0,
            new_memory_size=32,
        )
        + Op.CALLDATALOAD(0)
        + Op.PUSH0
    )

    loop = While(
        body=Op.POP(
            Op.CREATE(
                value=1 if value_bearing else 0,
                offset=32 - len(initcode),
                size=len(initcode),
                init_code_size=len(initcode),
            )
        ),
        condition=Op.PUSH1(1) + Op.ADD + Op.DUP1 + Op.DUP3 + Op.GT,
    )

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

    def calldata(iteration_count: int, start_iteration: int) -> bytes:
        del start_iteration
        return Hash(iteration_count)

    iteration_counts = list(
        attack_code.tx_iterations_by_gas_limit(
            fork=fork,
            gas_limit=gas_benchmark_value,
            calldata=calldata,
        )
    )
    num_iterations = sum(iteration_counts)

    attack_code_address = pre.deploy_contract(
        code=attack_code,
        balance=num_iterations if value_bearing else 0,
    )

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

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

    returned_to_creator = value_bearing and not beneficiary_is_self
    post = {
        attack_code_address: Account(
            balance=num_iterations if returned_to_creator else 0
        )
    }

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

Parametrized Test Cases

This test generates 3 parametrized test cases across 4 forks.