Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import pytest
from execution_testing import (
Account,
Address,
Alloc,
Fork,
Op,
Expand Down Expand Up @@ -104,6 +105,60 @@ def test_top_frame_state_charge(
state_test(pre=pre, tx=tx, post=post)


def test_top_frame_state_charge_empty_precompile(
fork: Fork,
pre: Alloc,
state_test: StateTestFiller,
) -> None:
"""
An empty precompile recipient is still empty per EIP-161, so a
value-moving transaction to it must pay the top-frame
``NEW_ACCOUNT`` state-gas charge.

The gas limit is one short of covering that state charge. Without
the charge, the transaction would reach the identity precompile and
transfer value, which makes this a direct regression test for a
precompile carve-out.
"""
sender_initial_balance = 10**18
sender = pre.fund_eoa(sender_initial_balance)
identity_precompile = Address(0x04)

value = 1
intrinsic_gas = fork.transaction_intrinsic_cost_calculator()(
sends_value=True,
recipient_type=RecipientType.PRECOMPILE,
return_cost_deducted_prior_execution=True,
)
top_frame_state_gas = fork.transaction_top_frame_state_gas(
sends_value=True,
recipient_type=RecipientType.EMPTY_ACCOUNT,
)
assert top_frame_state_gas > 0, (
"top-frame state gas must be non-zero for empty recipients"
)

gas_price = 1_000_000_000
gas_limit = intrinsic_gas + top_frame_state_gas - 1
tx = Transaction(
sender=sender,
to=identity_precompile,
value=value,
gas_limit=gas_limit,
gas_price=gas_price,
)

post = {
sender: Account(
nonce=1,
balance=sender_initial_balance - gas_limit * gas_price,
),
identity_precompile: None,
}

state_test(pre=pre, tx=tx, post=post)


@pytest.mark.parametrize("outcome", ["oog", "success", "evm_reverts"])
@pytest.mark.parametrize(
"value",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,34 +339,46 @@ def test_value_move_to_precompiles(
recipient_type=state_recipient_type,
)

# Precompile execution gas varies per precompile and input; pick a
# buffer large enough to cover the most expensive precompile in the
# matrix on top of the intrinsic and the (spilled) state charge.
precompile_execution_budget = 100_000
tx_gas_limit = (
intrinsic_gas + top_frame_state_gas + precompile_execution_budget
)
gas_price = 1_000_000_000

tx = Transaction(
sender=sender,
to=precompile,
value=value,
data=tx_data,
gas_limit=tx_gas_limit,
gas_price=gas_price,
)

# Exact sender balance is not checked because precompile execution
# gas varies; verify value receipt and sender nonce instead.
# Exact sender balance is generally not checked because precompile
# execution gas varies across the matrix. For identity with empty
# calldata, the execution gas is deterministic, so pin the exact
# balance to make the empty-precompile ``NEW_ACCOUNT`` charge a
# source-level assertion.
final_precompile_balance = pre_funded_amount + value
expected_precompile: Account | None
if final_precompile_balance > 0:
expected_precompile = Account(balance=final_precompile_balance)
else:
expected_precompile = None
expected_sender = Account(nonce=1)
if precompile == Address(0x04):
gas_costs = fork.gas_costs()
precompile_execution_gas = (
gas_costs.PRECOMPILE_IDENTITY_BASE
+ gas_costs.PRECOMPILE_IDENTITY_PER_WORD
* ((len(tx_data) + 31) // 32)
)
total_gas_cost = (
intrinsic_gas + top_frame_state_gas + precompile_execution_gas
)
expected_sender = Account(
nonce=1,
balance=(
sender_initial_balance - value - total_gas_cost * gas_price
),
)
post = {
sender: Account(nonce=1),
sender: expected_sender,
precompile: expected_precompile,
}

Expand Down
Loading