Skip to content

test_worst_modarith()

Documentation for tests/benchmark/test_worst_compute.py::test_worst_modarith@88e9fb8f.

Generate fixtures for these test cases for Osaka with:

fill -v tests/benchmark/test_worst_compute.py::test_worst_modarith -m benchmark

Test running a block with as many "op" instructions with arguments of the parametrized range. The test program consists of code segments evaluating the "op chain": mod[0] = calldataload(0) mod[1] = (fixed_arg op args[indexes[0]]) % mod[0] mod[2] = (fixed_arg op args[indexes[1]]) % mod[1] The "args" is a pool of 15 constants pushed to the EVM stack at the program start. The "fixed_arg" is the 0xFF...FF constant added to the EVM stack by PUSH32 just before executing the "op". 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
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
@pytest.mark.parametrize("mod_bits", [255, 191, 127, 63])
@pytest.mark.parametrize("op", [Op.ADDMOD, Op.MULMOD])
def test_worst_modarith(
    benchmark_test: BenchmarkTestFiller,
    pre: Alloc,
    fork: Fork,
    mod_bits: int,
    op: Op,
    gas_benchmark_value: int,
) -> None:
    """
    Test running a block with as many "op" instructions with arguments of the
    parametrized range.
    The test program consists of code segments evaluating the "op chain":
    mod[0] = calldataload(0)
    mod[1] = (fixed_arg op args[indexes[0]]) % mod[0]
    mod[2] = (fixed_arg op args[indexes[1]]) % mod[1]
    The "args" is a pool of 15 constants pushed to the EVM stack at the program
    start.
    The "fixed_arg" is the 0xFF...FF constant added to the EVM stack by PUSH32
    just before executing the "op".
    The order of accessing the numerators is selected in a way the mod value
    remains in the range as long as possible.
    """
    fixed_arg = 2**256 - 1
    num_args = 15

    max_code_size = fork.max_code_size()

    # 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 op chain. You can look
    # for a longer one by increasing the op_chain_len. This will activate the
    # while loop below.
    op_chain_len = 666
    match op, mod_bits:
        case Op.ADDMOD, 255:
            seed = 4
        case Op.ADDMOD, 191:
            seed = 2
        case Op.ADDMOD, 127:
            seed = 2
        case Op.ADDMOD, 63:
            seed = 64
        case Op.MULMOD, 255:
            seed = 5
        case Op.MULMOD, 191:
            seed = 389
        case Op.MULMOD, 127:
            seed = 5
        case Op.MULMOD, 63:
            # For this setup we were not able to find an op-chain longer than
            # 600.
            seed = 4193
            op_chain_len = 600
        case _:
            raise ValueError(f"{mod_bits}-bit {op} not supported.")

    while True:
        rng = random.Random(seed)
        args = [rng.randint(2**255, 2**256 - 1) for _ in range(num_args)]
        initial_mod = rng.randint(2 ** (mod_bits - 1), 2**mod_bits - 1)

        # Evaluate the op chain and collect the order of accessing numerators.
        op_fn = operator.add if op == Op.ADDMOD else operator.mul
        mod = initial_mod
        indexes: list[int] = []
        while mod >= mod_min and len(indexes) < op_chain_len:
            results = [op_fn(a, fixed_arg) % mod for a in args]
            # And pick the best one.
            i = max(range(len(results)), key=results.__getitem__)
            mod = results[i]
            indexes.append(i)

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

    code_constant_pool = sum((Op.PUSH32[n] for n in args), Bytecode())
    code_segment = (
        Op.CALLDATALOAD(0)
        + sum(make_dup(len(args) - i) + Op.PUSH32[fixed_arg] + op for i in indexes)
        + Op.POP
    )
    # Construct the final code. Because of the usage of PUSH32 the code segment
    # is very long, so don't try to include multiple of these.
    code = code_constant_pool + Op.JUMPDEST + code_segment + Op.JUMP(len(code_constant_pool))
    assert (max_code_size - len(code_segment)) < len(code) <= max_code_size

    tx = Transaction(
        to=pre.deploy_contract(code=code),
        data=initial_mod.to_bytes(32, byteorder="big"),
        gas_limit=gas_benchmark_value,
        sender=pre.fund_eoa(),
    )

    benchmark_test(tx=tx)

Parametrized Test Cases

This test case is only parametrized by fork.

Test ID (Abbreviated) op mod_bits
...fork_Prague-blockchain_test-op_ADDMOD-mod_bits_255 ADDMOD 255
...fork_Prague-blockchain_test-op_ADDMOD-mod_bits_191 ADDMOD 191
...fork_Prague-blockchain_test-op_ADDMOD-mod_bits_127 ADDMOD 127
...fork_Prague-blockchain_test-op_ADDMOD-mod_bits_63 ADDMOD 63
...fork_Prague-blockchain_test-op_MULMOD-mod_bits_255 MULMOD 255
...fork_Prague-blockchain_test-op_MULMOD-mod_bits_191 MULMOD 191
...fork_Prague-blockchain_test-op_MULMOD-mod_bits_127 MULMOD 127
...fork_Prague-blockchain_test-op_MULMOD-mod_bits_63 MULMOD 63
...fork_Osaka-blockchain_test-op_ADDMOD-mod_bits_255 ADDMOD 255
...fork_Osaka-blockchain_test-op_ADDMOD-mod_bits_191 ADDMOD 191
...fork_Osaka-blockchain_test-op_ADDMOD-mod_bits_127 ADDMOD 127
...fork_Osaka-blockchain_test-op_ADDMOD-mod_bits_63 ADDMOD 63
...fork_Osaka-blockchain_test-op_MULMOD-mod_bits_255 MULMOD 255
...fork_Osaka-blockchain_test-op_MULMOD-mod_bits_191 MULMOD 191
...fork_Osaka-blockchain_test-op_MULMOD-mod_bits_127 MULMOD 127
...fork_Osaka-blockchain_test-op_MULMOD-mod_bits_63 MULMOD 63