638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724 | @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
),
)
|