Skip to content

test_worst_mod()

Documentation for tests/benchmark/test_worst_compute.py::test_worst_mod@v5.0.0.

Generate fixtures for these test cases for Osaka with:

fill -v tests/benchmark/test_worst_compute.py::test_worst_mod --fork Osaka

Test running a block with as many MOD instructions with arguments of the parametrized range. The test program consists of code segments evaluating the "MOD chain": mod[0] = calldataload(0) mod[1] = numerators[indexes[0]] % mod[0] mod[2] = numerators[indexes[1]] % mod[1] ... The "numerators" is a pool of 15 constants pushed to the EVM stack at the program start. The order of accessing the numerators is selected in a way the mod value remains in the range as long as possible.

Source code in tests/benchmark/test_worst_compute.py
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
@pytest.mark.valid_from("Cancun")
@pytest.mark.parametrize("mod_bits", [255, 191, 127, 63])
@pytest.mark.parametrize("op", [Op.MOD, Op.SMOD])
def test_worst_mod(
    state_test: StateTestFiller,
    pre: Alloc,
    fork: Fork,
    mod_bits: int,
    op: Op,
    gas_benchmark_value: int,
):
    """
    Test running a block with as many MOD instructions with arguments of the parametrized range.
    The test program consists of code segments evaluating the "MOD chain":
    mod[0] = calldataload(0)
    mod[1] = numerators[indexes[0]] % mod[0]
    mod[2] = numerators[indexes[1]] % mod[1] ...
    The "numerators" is a pool of 15 constants pushed to the EVM stack at the program start.
    The order of accessing the numerators is selected in a way the mod value remains in the range
    as long as possible.
    """
    max_code_size = fork.max_code_size()

    # For SMOD we negate both numerator and modulus. The underlying computation is the same,
    # just the SMOD implementation will have to additionally handle the sign bits.
    # The result stays negative.
    should_negate = op == Op.SMOD

    num_numerators = 15
    numerator_bits = 256 if not should_negate else 255
    numerator_max = 2**numerator_bits - 1
    numerator_min = 2 ** (numerator_bits - 1)

    # Pick the modulus min value so that it is _unlikely_ to drop to the lower word count.
    assert mod_bits >= 63
    mod_min = 2 ** (mod_bits - 63)

    # Select the random seed giving the longest found MOD chain.
    # You can look for a longer one by increasing the numerators_min_len. This will activate
    # the while loop below.
    match op, mod_bits:
        case Op.MOD, 255:
            seed = 20393
            numerators_min_len = 750
        case Op.MOD, 191:
            seed = 25979
            numerators_min_len = 770
        case Op.MOD, 127:
            seed = 17671
            numerators_min_len = 750
        case Op.MOD, 63:
            seed = 29181
            numerators_min_len = 730
        case Op.SMOD, 255:
            seed = 4015
            numerators_min_len = 750
        case Op.SMOD, 191:
            seed = 17355
            numerators_min_len = 750
        case Op.SMOD, 127:
            seed = 897
            numerators_min_len = 750
        case Op.SMOD, 63:
            seed = 7562
            numerators_min_len = 720
        case _:
            raise ValueError(f"{mod_bits}-bit {op} not supported.")

    while True:
        rng = random.Random(seed)

        # Create the list of random numerators.
        numerators = [rng.randint(numerator_min, numerator_max) for _ in range(num_numerators)]

        # Create the random initial modulus.
        initial_mod = rng.randint(2 ** (mod_bits - 1), 2**mod_bits - 1)

        # Evaluate the MOD chain and collect the order of accessing numerators.
        mod = initial_mod
        indexes = []
        while mod >= mod_min:
            results = [n % mod for n in numerators]  # Compute results for each numerator.
            i = max(range(len(results)), key=results.__getitem__)  # And pick the best one.
            mod = results[i]
            indexes.append(i)

        assert len(indexes) > numerators_min_len  # Disable if you want to find longer MOD chains.
        if len(indexes) > numerators_min_len:
            break
        seed += 1
        print(f"{seed=}")

    # TODO: Don't use fixed PUSH32. Let Bytecode helpers to select optimal push opcode.
    code_constant_pool = sum((Op.PUSH32[n] for n in numerators), Bytecode())
    code_prefix = code_constant_pool + Op.JUMPDEST
    code_suffix = Op.JUMP(len(code_constant_pool))
    code_body_len = max_code_size - len(code_prefix) - len(code_suffix)
    code_segment = (
        Op.CALLDATALOAD(0) + sum(make_dup(len(numerators) - i) + op for i in indexes) + Op.POP
    )
    code = (
        code_prefix
        # TODO: Add int * Bytecode support
        + sum(code_segment for _ in range(code_body_len // len(code_segment)))
        + code_suffix
    )
    assert (max_code_size - len(code_segment)) < len(code) <= max_code_size

    input_value = initial_mod if not should_negate else neg(initial_mod)
    tx = Transaction(
        to=pre.deploy_contract(code=code),
        data=input_value.to_bytes(32, byteorder="big"),
        gas_limit=gas_benchmark_value,
        sender=pre.fund_eoa(),
    )

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

Parametrized Test Cases

The interactive table below is also available as a standalone page.

Test ID (Abbreviated) op mod_bits
...fork_Cancun-state_test-op_MOD-mod_bits_255 MOD 255
...fork_Cancun-state_test-op_MOD-mod_bits_191 MOD 191
...fork_Cancun-state_test-op_MOD-mod_bits_127 MOD 127
...fork_Cancun-state_test-op_MOD-mod_bits_63 MOD 63
...fork_Cancun-state_test-op_SMOD-mod_bits_255 SMOD 255
...fork_Cancun-state_test-op_SMOD-mod_bits_191 SMOD 191
...fork_Cancun-state_test-op_SMOD-mod_bits_127 SMOD 127
...fork_Cancun-state_test-op_SMOD-mod_bits_63 SMOD 63
...fork_Cancun-blockchain_test_from_state_test-op_MOD-mod_bits_255 MOD 255
...fork_Cancun-blockchain_test_from_state_test-op_MOD-mod_bits_191 MOD 191
...fork_Cancun-blockchain_test_from_state_test-op_MOD-mod_bits_127 MOD 127
...fork_Cancun-blockchain_test_from_state_test-op_MOD-mod_bits_63 MOD 63
...fork_Cancun-blockchain_test_from_state_test-op_SMOD-mod_bits_255 SMOD 255
...fork_Cancun-blockchain_test_from_state_test-op_SMOD-mod_bits_191 SMOD 191
...fork_Cancun-blockchain_test_from_state_test-op_SMOD-mod_bits_127 SMOD 127
...fork_Cancun-blockchain_test_from_state_test-op_SMOD-mod_bits_63 SMOD 63
...fork_Prague-state_test-op_MOD-mod_bits_255 MOD 255
...fork_Prague-state_test-op_MOD-mod_bits_191 MOD 191
...fork_Prague-state_test-op_MOD-mod_bits_127 MOD 127
...fork_Prague-state_test-op_MOD-mod_bits_63 MOD 63
...fork_Prague-state_test-op_SMOD-mod_bits_255 SMOD 255
...fork_Prague-state_test-op_SMOD-mod_bits_191 SMOD 191
...fork_Prague-state_test-op_SMOD-mod_bits_127 SMOD 127
...fork_Prague-state_test-op_SMOD-mod_bits_63 SMOD 63
...fork_Prague-blockchain_test_from_state_test-op_MOD-mod_bits_255 MOD 255
...fork_Prague-blockchain_test_from_state_test-op_MOD-mod_bits_191 MOD 191
...fork_Prague-blockchain_test_from_state_test-op_MOD-mod_bits_127 MOD 127
...fork_Prague-blockchain_test_from_state_test-op_MOD-mod_bits_63 MOD 63
...fork_Prague-blockchain_test_from_state_test-op_SMOD-mod_bits_255 SMOD 255
...fork_Prague-blockchain_test_from_state_test-op_SMOD-mod_bits_191 SMOD 191
...fork_Prague-blockchain_test_from_state_test-op_SMOD-mod_bits_127 SMOD 127
...fork_Prague-blockchain_test_from_state_test-op_SMOD-mod_bits_63 SMOD 63
...fork_Osaka-state_test-op_MOD-mod_bits_255 MOD 255
...fork_Osaka-state_test-op_MOD-mod_bits_191 MOD 191
...fork_Osaka-state_test-op_MOD-mod_bits_127 MOD 127
...fork_Osaka-state_test-op_MOD-mod_bits_63 MOD 63
...fork_Osaka-state_test-op_SMOD-mod_bits_255 SMOD 255
...fork_Osaka-state_test-op_SMOD-mod_bits_191 SMOD 191
...fork_Osaka-state_test-op_SMOD-mod_bits_127 SMOD 127
...fork_Osaka-state_test-op_SMOD-mod_bits_63 SMOD 63
...fork_Osaka-blockchain_test_from_state_test-op_MOD-mod_bits_255 MOD 255
...fork_Osaka-blockchain_test_from_state_test-op_MOD-mod_bits_191 MOD 191
...fork_Osaka-blockchain_test_from_state_test-op_MOD-mod_bits_127 MOD 127
...fork_Osaka-blockchain_test_from_state_test-op_MOD-mod_bits_63 MOD 63
...fork_Osaka-blockchain_test_from_state_test-op_SMOD-mod_bits_255 SMOD 255
...fork_Osaka-blockchain_test_from_state_test-op_SMOD-mod_bits_191 SMOD 191
...fork_Osaka-blockchain_test_from_state_test-op_SMOD-mod_bits_127 SMOD 127
...fork_Osaka-blockchain_test_from_state_test-op_SMOD-mod_bits_63 SMOD 63