@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement()
@pytest.mark.with_all_create_opcodes()
@pytest.mark.parametrize(
"init_code_size",
[
pytest.param(0, id="empty"),
pytest.param(32, id="one_word"),
pytest.param(33, id="two_words"),
pytest.param(96, id="three_words"),
],
)
def test_create_execution_gas(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
create_opcode: Op,
init_code_size: int,
) -> None:
"""
Measure the execution gas of CREATE/CREATE2 and assert the schedule.
The EIP-8038 *execution* dimension is ``CREATE_ACCESS`` plus the
EIP-3860 init code word cost plus, for ``CREATE2`` only, an
additional keccak word cost. The EIP-8037
account-creation state gas is excluded by subtracting
``create_state_gas(0)``.
"""
# Isolate the execution dimension: opcode total minus its account
# creation state gas (the only state component carried by the CREATE
# opcode itself; code deposit is charged on RETURN inside initcode).
create_meta = create_opcode(init_code_size=init_code_size)
execution_gas = create_meta.gas_cost(fork) - fork.create_state_gas(
code_size=0
)
# Equivalent isolation via the execution_cost helper.
assert execution_gas == create_meta.execution_cost(fork)
# Runtime confirmation via CodeGasMeasure: a factory whose CREATE
# deploys empty code, so no code-deposit state gas is charged and the
# only state component is the account-creation gas funded from the
# reservoir. The initcode is brought into memory BEFORE the measured
# window, so the memory-expansion charge is excluded; the measured
# value is the CREATE opcode's execution cost exactly. The overhead
# subtracts the create-call argument pushes (the create leaves one
# stack item, its result).
#
# The initcode is all-zero bytes (`STOP`), so the child frame halts
# immediately consuming zero gas and deposits empty code. This keeps
# the measured value the CREATE opcode's own execution cost, with no
# child-execution gas folded in. `init_code_size` still drives the
# opcode's per-init-word charge.
padded_init = b"\x00" * init_code_size
create_call = (
Op.CREATE2(value=0, offset=0, size=init_code_size, salt=0)
if create_opcode == Op.CREATE2
else Op.CREATE(value=0, offset=0, size=init_code_size)
)
push_cost = Op.PUSH1(0).execution_cost(fork)
arg_pushes = (4 if create_opcode == Op.CREATE2 else 3) * push_cost
memory_setup = (
Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE, new_memory_size=init_code_size)
if init_code_size
else Bytecode()
)
storage = Storage()
measure = CodeGasMeasure(
code=create_call,
overhead_cost=arg_pushes,
extra_stack_items=1,
sstore_key=storage.store_next(execution_gas, "create_execution_gas"),
)
factory = pre.deploy_contract(code=memory_setup + measure)
tx = Transaction(
to=factory,
data=padded_init,
# Reservoir funds the account-creation state gas; leaving
# gas_limit unset keeps `Op.GAS` honest about gas_left.
state_gas_reservoir=fork.create_state_gas(code_size=0),
sender=pre.fund_eoa(),
)
post = {factory: Account(storage=storage)}
state_test(pre=pre, post=post, tx=tx)