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 @@ -7,6 +7,7 @@
Alloc,
Bytes,
Fork,
Op,
RecipientType,
StateTestFiller,
Transaction,
Expand All @@ -18,7 +19,11 @@
from ...prague.eip7623_increase_calldata_cost.helpers import (
find_floor_cost_threshold,
)
from .helpers import EOA_INITIAL_BALANCE
from .helpers import (
EOA_INITIAL_BALANCE,
AuthorizationAction,
build_authorization,
)
from .spec import ref_spec_2780

REFERENCE_SPEC_GIT_PATH = ref_spec_2780.git_path
Expand Down Expand Up @@ -323,3 +328,102 @@ def test_calldata_floor_contract_creation(
}

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


@pytest.mark.parametrize(
"outcome",
[
pytest.param("floor_binds", id="floor_binds"),
pytest.param(
"below_floor",
id="below_floor_rejected",
marks=pytest.mark.exception_test,
),
],
)
def test_calldata_floor_with_authorizations(
fork: Fork,
pre: Alloc,
state_test: StateTestFiller,
outcome: str,
) -> None:
"""
A data-heavy type-4 transaction whose calldata floor exceeds the
full authorization total: the intrinsic plus the authorization's
top-frame execution and state charges.
"""
sender = pre.fund_eoa()
recipient = pre.deploy_contract(code=Op.STOP)
scenario = build_authorization(
pre, AuthorizationAction.SETS_NEW_DELEGATION
)
authorization_list = [scenario.authorization]

intrinsic_calc = fork.transaction_intrinsic_cost_calculator()
floor_calc = fork.transaction_data_floor_cost_calculator()
top_frame_gas = fork.transaction_top_frame_gas_calculator()(
recipient_type=RecipientType.CONTRACT,
authorizations=authorization_list,
)
top_frame_state_gas = fork.transaction_top_frame_state_gas(
recipient_type=RecipientType.CONTRACT,
authorizations=authorization_list,
)

def total(byte_count: int) -> int:
return (
intrinsic_calc(
calldata=b"\x00" * byte_count,
recipient_type=RecipientType.CONTRACT,
authorization_list_or_count=authorization_list,
return_cost_deducted_prior_execution=True,
)
+ top_frame_gas
+ top_frame_state_gas
)

def floor(byte_count: int) -> int:
return floor_calc(
data=b"\x00" * byte_count,
recipient_type=RecipientType.CONTRACT,
)

threshold = find_floor_cost_threshold(
floor_data_gas_cost_calculator=floor,
intrinsic_gas_cost_calculator=total,
)
byte_count = threshold + 1
calldata = Bytes(b"\x00" * byte_count)
calldata_floor = floor(byte_count)
assert calldata_floor > total(byte_count), (
"the calldata floor must dominate the full authorization total"
)

post: dict[Address, Account | None]
if outcome == "below_floor":
tx = Transaction(
sender=sender,
to=recipient,
data=calldata,
authorization_list=authorization_list,
gas_limit=calldata_floor - 1,
error=TransactionException.INTRINSIC_GAS_BELOW_FLOOR_GAS_COST,
)
post = {scenario.authority: scenario.original_account}
else:
tx = Transaction(
sender=sender,
to=recipient,
data=calldata,
authorization_list=authorization_list,
gas_limit=calldata_floor,
expected_receipt=TransactionReceipt(
cumulative_gas_used=calldata_floor,
),
)
post = {
sender: Account(nonce=1),
scenario.authority: scenario.applied_account,
}

state_test(pre=pre, tx=tx, post=post)
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,18 @@
Account,
Address,
Alloc,
AuthorizationTuple,
Block,
BlockchainTestFiller,
Op,
RecipientType,
Transaction,
TransactionReceipt,
TransitionFork,
compute_create_address,
)

from ...prague.eip7702_set_code_tx.spec import Spec as Spec7702
from .helpers import EOA_INITIAL_BALANCE
from .spec import ref_spec_2780

Expand Down Expand Up @@ -269,3 +272,99 @@ def test_creation_tx_intrinsic_across_amsterdam_transition(
post[created] = Account(nonce=1, balance=value, code=b"")

blockchain_test(pre=pre, blocks=blocks, post=post)


def test_setcode_tx_across_amsterdam_transition(
blockchain_test: BlockchainTestFiller,
pre: Alloc,
fork: TransitionFork,
) -> None:
"""
Pin the EIP-2780 authorization repricing across the Amsterdam
boundary.
"""
gas_price = 1_000_000_000

pre_costs = fork.fork_at(timestamp=PRE_FORK_TIMESTAMP).gas_costs()
post_costs = fork.fork_at(timestamp=POST_FORK_TIMESTAMP).gas_costs()

# Pre-fork: EIP-7702 charges the full per-authorization cost in the
# intrinsic; an empty authority earns no existing-authority refund.
expected_pre = pre_costs.TX_BASE + pre_costs.AUTH_PER_EMPTY_ACCOUNT
# Post-fork: EIP-2780 decomposition across the three charge layers.
expected_post = (
post_costs.TX_BASE
+ post_costs.COLD_ACCOUNT_ACCESS
+ post_costs.EXECUTION_PER_AUTH_BASE_COST
+ post_costs.ACCOUNT_WRITE
+ post_costs.NEW_ACCOUNT
+ post_costs.AUTH_BASE
)

timestamps = [PRE_FORK_TIMESTAMP, POST_FORK_TIMESTAMP]
expected_totals = [expected_pre, expected_post]
blocks = []
post: dict[Address, Account] = {}

for timestamp, expected_total in zip(
timestamps, expected_totals, strict=True
):
sub_fork = fork.fork_at(timestamp=timestamp)
recipient = pre.deploy_contract(code=Op.STOP)
delegate_to = pre.deploy_contract(code=Op.STOP)
authority = pre.fund_eoa(amount=0)
authorization = AuthorizationTuple(
address=delegate_to,
nonce=0,
signer=authority,
creates_account=True,
)

intrinsic_gas = sub_fork.transaction_intrinsic_cost_calculator()(
recipient_type=RecipientType.CONTRACT,
authorization_list_or_count=[authorization],
return_cost_deducted_prior_execution=True,
)
top_frame_gas = sub_fork.transaction_top_frame_gas_calculator()(
recipient_type=RecipientType.CONTRACT,
authorizations=[authorization],
)
top_frame_state_gas = sub_fork.transaction_top_frame_state_gas(
recipient_type=RecipientType.CONTRACT,
authorizations=[authorization],
)
total_gas = intrinsic_gas + top_frame_gas + top_frame_state_gas
assert total_gas == expected_total, (
f"set-code total at timestamp {timestamp} ({sub_fork}) is "
f"{total_gas}, expected {expected_total}"
)

sender_initial_balance = 10**18
sender = pre.fund_eoa(sender_initial_balance)

# Both recipient and delegate run ``STOP`` (no execution gas),
# so the receipt pins the intrinsic and top-frame layers alone.
tx = Transaction(
sender=sender,
to=recipient,
authorization_list=[authorization],
gas_limit=total_gas,
max_fee_per_gas=gas_price,
max_priority_fee_per_gas=gas_price,
expected_receipt=TransactionReceipt(
cumulative_gas_used=total_gas,
),
)
blocks.append(Block(timestamp=timestamp, txs=[tx]))

post[sender] = Account(
nonce=1,
balance=sender_initial_balance - total_gas * gas_price,
)
post[authority] = Account(
nonce=1,
balance=0,
code=Spec7702.delegation_designation(delegate_to),
)

blockchain_test(pre=pre, blocks=blocks, post=post)
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,21 @@
Alloc,
AuthorizationTuple,
Fork,
Hash,
Op,
RecipientType,
StateTestFiller,
Transaction,
TransactionException,
add_kzg_version,
)

from ...cancun.eip4844_blobs.spec import Spec as EIP4844_Spec
from .helpers import (
EOA_INITIAL_BALANCE,
RECIPIENT_TYPES_NON_CREATE,
AuthorizationAction,
build_authorization,
setup_target,
)
from .spec import ref_spec_2780
Expand Down Expand Up @@ -177,3 +182,58 @@ def test_intrinsic_gas_floor_boundary_with_authorizations(
)

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


@pytest.mark.exception_test
@pytest.mark.with_all_tx_types
def test_intrinsic_gas_floor_boundary_all_tx_types(
fork: Fork,
pre: Alloc,
state_test: StateTestFiller,
tx_type: int,
) -> None:
"""
Reject every transaction type when ``gas_limit = intrinsic_gas - 1``.

Each type layers its own intrinsic components on the shared
value-transfer shape -- ``EXECUTION_PER_AUTH_BASE_COST`` for type 4 --
and the pre-execution check must reject one gas below the per-type
total. Blob gas is priced in its own dimension, so a type-3
transaction's execution-gas boundary is identical to type 2.
"""
value = 1
sender = pre.fund_eoa()
target = pre.fund_eoa(amount=EOA_INITIAL_BALANCE)

scenario = (
build_authorization(pre, AuthorizationAction.SETS_NEW_DELEGATION)
if tx_type == 4
else None
)
authorizations = [scenario.authorization] if scenario else []

blob_versioned_hashes = (
add_kzg_version([Hash(1)], EIP4844_Spec.BLOB_COMMITMENT_VERSION_KZG)
if tx_type == 3
else None
)

intrinsic_gas = fork.transaction_intrinsic_cost_calculator()(
sends_value=True,
recipient_type=RecipientType.EOA,
authorization_list_or_count=authorizations,
return_cost_deducted_prior_execution=True,
)

tx = Transaction(
ty=tx_type,
sender=sender,
to=target,
value=value,
authorization_list=authorizations or None,
blob_versioned_hashes=blob_versioned_hashes,
gas_limit=intrinsic_gas - 1,
error=TransactionException.INTRINSIC_GAS_TOO_LOW,
)

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