Skip to content

test_jumpdest_analysis()

Documentation for tests/benchmark/compute/scenario/test_mix_operations.py::test_jumpdest_analysis@7b8124a7.

Generate fixtures for these test cases for Osaka with:

fill -v tests/benchmark/compute/scenario/test_mix_operations.py::test_jumpdest_analysis --gas-benchmark-values 1

Test the jumpdest analysis performance of the initcode.

This benchmark places a very long initcode in the memory and then invoke CREATE instructions with this initcode up to the block gas limit. The initcode itself has minimal execution time but forces the EVM to perform the full jumpdest analysis on the parametrized byte pattern. The initicode is modified by mixing-in the returned create address between CREATE invocations to prevent caching.

Source code in tests/benchmark/compute/scenario/test_mix_operations.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
@pytest.mark.parametrize(
    "pattern",
    [
        Op.STOP,
        Op.JUMPDEST,
        Op.PUSH1[bytes(Op.JUMPDEST)],
        Op.PUSH2[bytes(Op.JUMPDEST + Op.JUMPDEST)],
        Op.PUSH1[bytes(Op.JUMPDEST)] + Op.JUMPDEST,
        Op.PUSH2[bytes(Op.JUMPDEST + Op.JUMPDEST)] + Op.JUMPDEST,
    ],
    ids=lambda x: x.hex(),
)
def test_jumpdest_analysis(
    benchmark_test: BenchmarkTestFiller,
    fork: Fork,
    pattern: Bytecode,
) -> None:
    """
    Test the jumpdest analysis performance of the initcode.

    This benchmark places a very long initcode in the memory and then invoke
    CREATE instructions with this initcode up to the block gas limit. The
    initcode itself has minimal execution time but forces the EVM to perform
    the full jumpdest analysis on the parametrized byte pattern. The initicode
    is modified by mixing-in the returned create address between CREATE
    invocations to prevent caching.
    """
    initcode_size = fork.max_initcode_size()

    # Expand the initcode pattern to the transaction data so it can be used in
    # CALLDATACOPY in the main contract. TODO: tune the tx_data_len param.
    tx_data_len = 1024
    tx_data = pattern * (tx_data_len // len(pattern))
    tx_data += (tx_data_len - len(tx_data)) * bytes(Op.JUMPDEST)
    assert len(tx_data) == tx_data_len
    assert initcode_size % len(tx_data) == 0

    # Prepare the initcode in memory.
    code_prepare_initcode = sum(
        (
            Op.CALLDATACOPY(
                dest_offset=i * len(tx_data), offset=0, size=Op.CALLDATASIZE
            )
            for i in range(initcode_size // len(tx_data))
        ),
        Bytecode(),
    )

    # At the start of the initcode execution, jump to the last opcode.
    # This forces EVM to do the full jumpdest analysis.
    initcode_prefix = Op.JUMP(initcode_size - 1)
    code_prepare_initcode += Op.MSTORE(
        0, Op.PUSH32[bytes(initcode_prefix).ljust(32, bytes(Op.JUMPDEST))]
    )

    # Make sure the last opcode in the initcode is JUMPDEST.
    code_prepare_initcode += Op.MSTORE(
        initcode_size - 32, Op.PUSH32[bytes(Op.JUMPDEST) * 32]
    )

    attack_block = (
        Op.PUSH1[len(initcode_prefix)]
        + Op.MSTORE
        + Op.CREATE(value=Op.PUSH0, offset=Op.PUSH0, size=Op.MSIZE)
    )

    setup = code_prepare_initcode + Op.PUSH0

    benchmark_test(
        code_generator=JumpLoopGenerator(
            setup=setup,
            attack_block=attack_block,
            tx_kwargs={"data": tx_data},
        ),
    )

Parametrized Test Cases

This test generates 5 parametrized test cases across 2 forks.