Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions src/ethereum/forks/amsterdam/vm/eoa_delegation.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,10 +208,11 @@ def set_delegation(evm: Evm) -> None:
account leaf does not yet exist.
- ``GasCosts.ACCOUNT_WRITE`` (regular) when applying the
authorization is the transaction's first write to the authority's
leaf. The sender's leaf was already written at inclusion (priced
into ``TX_BASE``), so a self-sponsored authority pays no
``ACCOUNT_WRITE``, and repeated authorizations on one authority
pay it once.
leaf. Writes the transaction already prices elsewhere are
exempt: the sender's, covered by ``TX_BASE``, and, for a
value-bearing transaction, the recipient's, covered by
``TX_VALUE_COST``. Repeated authorizations on one authority pay
it once.
- ``StateGasCosts.AUTH_BASE`` (state) when a net-new delegation
indicator is written: the authority held no delegation before the
transaction, none was set for it earlier in the transaction, and
Expand All @@ -232,9 +233,11 @@ def set_delegation(evm: Evm) -> None:
"""
message = evm.message
tx_state = message.tx_env.state
# Accounts this transaction has already written: the sender's leaf
# was written at inclusion (nonce bump and fee deduction). The
# recipient is written when value is transferred.
# Accounts whose write the transaction has already priced: the
# sender's leaf was written at inclusion (nonce bump and fee
# deduction), and a value-bearing transaction prepays the
# recipient's balance write -- the transfer itself only happens at
# frame entry, after these charges.
written_accounts: Set[Address] = {message.tx_env.origin}
if evm.message.tx_env.value > U256(0):
written_accounts.add(evm.message.current_target)
Expand Down
7 changes: 7 additions & 0 deletions src/ethereum/forks/amsterdam/vm/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,13 @@ def prepare_dispatch(evm: Evm) -> None:
cold account access and pointing the frame at the delegated
code.

The creation target is checked against the transaction pre-state:
``process_create_message`` has already bumped the target's nonce
by the time this runs, so a live check would always see the
account. The recipient check is live, so an authority
materialized earlier in the transaction is not charged
``NEW_ACCOUNT`` again.

This function must not mutate the transaction state. Every charge
here pays for state that only materializes inside the dispatched
frame and rolls back with it, so these charges stay refillable --
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1373,6 +1373,9 @@ def test_create_tx_header_gas_used(
floor = fork.transaction_data_floor_cost_calculator()(
data=bytes(initcode), contract_creation=True
)
assert floor > regular_intrinsic, (
"the floor must bind for this arm to pin floor-in-header"
)
expected_gas_used = max(regular_intrinsic, floor)
else:
# For a minimal CREATE tx deploying Op.STOP (1 byte),
Expand Down Expand Up @@ -2144,12 +2147,15 @@ def test_create_account_charge_reduces_child_gas(


@pytest.mark.parametrize(
"init_code",
("init_code", "floor_binds"),
[
pytest.param(
Op.REVERT(0, 10_000, new_memory_size=10_000), id="revert"
Op.REVERT(0, 10_000, new_memory_size=10_000),
False,
id="revert",
),
pytest.param(Op.INVALID, id="halt"),
pytest.param(Op.REVERT(0, 0), True, id="revert_floor_bound"),
pytest.param(Op.INVALID, None, id="halt"),
],
)
@pytest.mark.valid_from("EIP8037")
Expand All @@ -2158,6 +2164,7 @@ def test_failed_create_tx_refills_top_frame_new_account(
pre: Alloc,
fork: Fork,
init_code: Bytecode,
floor_binds: bool | None,
) -> None:
"""
Verify the top-frame NEW_ACCOUNT of a creation tx is refilled when the
Expand All @@ -2171,10 +2178,11 @@ def test_failed_create_tx_refills_top_frame_new_account(

* REVERT preserves ``gas_left`` and ``refill_frame_state_gas`` returns
the spilled ``NEW_ACCOUNT`` to it, so the state block nets to zero
and only the regular consumption counts as work. The tiny init code
leaves the decomposed calldata floor above that consumption, so the
amount billed (receipt) is pinned to the floor while the header
excludes the floor top-up.
and only the regular consumption counts as work. The calldata floor
tops up the billed amount and the block-level regular gas alike, so
receipt and header agree at the greater of consumption and floor:
the memory expansion keeps ``revert`` above the floor, while the
bare ``revert_floor_bound`` pins the floor in both.
* HALT (INVALID) refills the spilled ``NEW_ACCOUNT`` to ``gas_left``
and then burns all of it, so the sender pays the full ``gas_limit``.
"""
Expand All @@ -2199,19 +2207,19 @@ def test_failed_create_tx_refills_top_frame_new_account(
# Exceptional halt burns all gas_left (the refilled NEW_ACCOUNT
# included).
expected_gas_used = gas_limit
expected_header_gas = gas_limit
else:
# REVERT refills the spilled NEW_ACCOUNT, netting the state block
# to zero, so only the regular consumption counts as work.
# to zero, so only the regular consumption counts as work. The
# calldata floor binds the billed amount and the block-level
# regular gas alike, so receipt and header agree either way.
regular_consumed = intrinsic_regular + init_code.regular_cost(fork)
# The tiny init code leaves the decomposed calldata floor above
# the regular gas consumed: the receipt bills at the floor, while
# the header's regular-gas accounting excludes the floor top-up.
floor = fork.transaction_data_floor_cost_calculator()(
data=bytes(init_code), contract_creation=True
)
assert (floor > regular_consumed) == floor_binds, (
"init code lands on the wrong side of the floor"
)
expected_gas_used = max(regular_consumed, floor)
expected_header_gas = regular_consumed

sender = pre.fund_eoa()
created = compute_create_address(address=sender, nonce=0)
Expand All @@ -2230,7 +2238,7 @@ def test_failed_create_tx_refills_top_frame_new_account(
pre=pre,
post={created: Account.NONEXISTENT},
tx=tx,
blockchain_test_header_verify=Header(gas_used=expected_header_gas),
blockchain_test_header_verify=Header(gas_used=expected_gas_used),
)


Expand Down
Loading