Skip to content

test_creates_collisions()

Documentation for tests/benchmark/compute/instruction/test_system.py::test_creates_collisions@8db70f93.

Generate fixtures for these test cases for Amsterdam with:

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

Benchmark CREATE and CREATE2 instructions with collisions.

Source code in tests/benchmark/compute/instruction/test_system.py
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
@pytest.mark.parametrize(
    "opcode",
    [
        Op.CREATE,
        Op.CREATE2,
    ],
)
@pytest.mark.pre_alloc_mutable
def test_creates_collisions(
    benchmark_test: BenchmarkTestFiller,
    pre: Alloc,
    fork: Fork,
    opcode: Op,
    gas_benchmark_value: int,
) -> None:
    """Benchmark CREATE and CREATE2 instructions with collisions."""
    # We deploy a "proxy contract" which is the contract that will be called in
    # a loop using all the gas in the block. This "proxy contract" is the one
    # executing CREATE2 failing with a collision. The reason why we need a
    # "proxy contract" is that CREATE(2) failing with a collision will consume
    # all the available gas. If we try to execute the CREATE(2) directly
    # without being wrapped **and capped in gas** in a previous CALL, we would
    # run out of gas very fast!
    # The proxy contract calls CREATE(2) with empty initcode. The current call
    # frame gas will be exhausted because of the collision. For this reason the
    # caller will carefully give us the minimal gas necessary to execute the
    # CREATE(2) and not waste any extra gas in the CREATE(2)-failure.
    # Note that these CREATE(2) calls will fail because in (**) below we pre-
    # alloc contracts with the same address as the ones that CREATE(2) will try
    # to create.
    proxy_contract_code = (
        Op.CREATE2(
            value=Op.PUSH0, salt=Op.PUSH0, offset=Op.PUSH0, size=Op.PUSH0
        )
        if opcode == Op.CREATE2
        else Op.CREATE(value=Op.PUSH0, offset=Op.PUSH0, size=Op.PUSH0)
    )
    proxy_contract = pre.deploy_contract(code=proxy_contract_code)

    # The CALL to the proxy contract needs at a minimum gas corresponding to
    # the CREATE(2) plus extra required PUSH0s for arguments.
    min_gas_required = proxy_contract_code.gas_cost(fork)
    setup = Op.PUSH20(proxy_contract) + Op.PUSH3(min_gas_required)
    attack_block = Op.POP(
        # DUP7 refers to the PUSH3 above.
        # DUP7 refers to the proxy contract address.
        Op.CALL(gas=Op.DUP7, address=Op.DUP7)
    )

    # (**) We deploy the contract that CREATE(2) will attempt to create so any
    # attempt will fail.
    if opcode == Op.CREATE2:
        addr = compute_create2_address(
            address=proxy_contract, salt=0, initcode=[]
        )
        pre.deploy_contract(address=addr, code=Op.INVALID)
    else:
        # Heuristic to have an upper bound.
        creation_cost = proxy_contract_code.gas_cost(fork)
        max_contract_count = 2 * gas_benchmark_value // creation_cost
        for nonce in range(max_contract_count):
            addr = compute_create_address(address=proxy_contract, nonce=nonce)
            pre.deploy_contract(address=addr, code=Op.INVALID)

    benchmark_test(
        target_opcode=opcode,
        code_generator=JumpLoopGenerator(
            setup=setup, attack_block=attack_block
        ),
    )

Parametrized Test Cases

This test generates 2 parametrized test cases across 3 forks.