@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,
fixed_opcode_count: float | None,
) -> 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.
# The collision targets pre-exist (**), so per EIP-8037 the
# CREATE(2) never charges NEW_ACCOUNT state gas.
proxy_contract_code = (
Op.CREATE2(
value=Op.PUSH0,
salt=Op.PUSH0,
offset=Op.PUSH0,
size=Op.PUSH0,
# gas accounting
account_new=False,
)
if opcode == Op.CREATE2
else Op.CREATE(
value=Op.PUSH0,
offset=Op.PUSH0,
size=Op.PUSH0,
# gas accounting
account_new=False,
)
)
proxy_contract = pre.deploy_contract(code=proxy_contract_code)
min_gas_required = proxy_contract_code.execution_cost(
fork
) + proxy_contract_code.state_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:
creation_cost = proxy_contract_code.execution_cost(fork)
max_contract_count = (
2 * gas_benchmark_value // creation_cost
if fixed_opcode_count is None
else int(fixed_opcode_count * 1000)
)
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
),
)