From df8f509eef70cb0a2d3d50652c294694efaf6ae2 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Thu, 23 Jul 2026 09:59:50 +0200 Subject: [PATCH] feat(specs,amsterdam): fold transfer log cost into value transfer cost Align the EIP-2780 implementation with the changes proposed in https://github.com/ethereum/EIPs/pull/11997 --- .../forks/forks/eips/amsterdam/eip_2780.py | 20 ++++------------ .../src/execution_testing/forks/gas_costs.py | 1 - src/ethereum/forks/amsterdam/transactions.py | 10 +++----- src/ethereum/forks/amsterdam/vm/gas.py | 3 +-- .../eip2780_reduce_intrinsic_tx_gas/spec.py | 2 +- .../test_calldata_floor.py | 12 ++++------ .../test_fork_transition.py | 11 +++------ .../test_value_moving_transactions.py | 24 +++++++------------ 8 files changed, 26 insertions(+), 57 deletions(-) diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py index 28bbcb60b84..c001d74e6e4 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py @@ -40,8 +40,7 @@ def gas_costs(cls) -> GasCosts: return replace( parent, TX_BASE=12_000, - TRANSFER_LOG_COST=1_756, - TX_VALUE_COST=4_244, + TX_VALUE_COST=6_000, ) @classmethod @@ -75,14 +74,10 @@ def fn( # CREATE_ACCESS regular gas; TX_CREATE folds in the # NEW_ACCOUNT state gas, which the floor excludes. floor += gas_costs.TX_CREATE - gas_costs.NEW_ACCOUNT - if sends_value: - floor += gas_costs.TRANSFER_LOG_COST elif not is_self_transfer: floor += gas_costs.COLD_ACCOUNT_ACCESS if sends_value: - floor += ( - gas_costs.TRANSFER_LOG_COST + gas_costs.TX_VALUE_COST - ) + floor += gas_costs.TX_VALUE_COST return floor return fn @@ -97,9 +92,8 @@ def transaction_intrinsic_cost_calculator( Non-create, non-self targets pay ``COLD_ACCOUNT_ACCESS`` unconditionally; access lists do not warm transaction-level - accounts. Value-bearing transactions pay - ``TRANSFER_LOG_COST`` plus ``TX_VALUE_COST``; self-transfers - suppress the value-transfer charge entirely. + accounts. Value-bearing transactions pay ``TX_VALUE_COST``; + self-transfers suppress the value-transfer charge entirely. """ super_fn = super(EIP2780, cls).transaction_intrinsic_cost_calculator() gas_costs = cls.gas_costs() @@ -147,14 +141,10 @@ def fn( # remove it here, mirroring value transfer to an empty # account whose NEW_ACCOUNT is likewise top-frame. intrinsic_cost -= gas_costs.NEW_ACCOUNT - if sends_value: - intrinsic_cost += gas_costs.TRANSFER_LOG_COST elif not is_self_transfer: intrinsic_cost += gas_costs.COLD_ACCOUNT_ACCESS if sends_value: - intrinsic_cost += ( - gas_costs.TRANSFER_LOG_COST + gas_costs.TX_VALUE_COST - ) + intrinsic_cost += gas_costs.TX_VALUE_COST if return_cost_deducted_prior_execution: return intrinsic_cost diff --git a/packages/testing/src/execution_testing/forks/gas_costs.py b/packages/testing/src/execution_testing/forks/gas_costs.py index 0113895108f..81e660fdaa5 100644 --- a/packages/testing/src/execution_testing/forks/gas_costs.py +++ b/packages/testing/src/execution_testing/forks/gas_costs.py @@ -38,7 +38,6 @@ class GasCosts: NEW_ACCOUNT: int ACCOUNT_WRITE: int = 0 CREATE_ACCESS: int = 0 - TRANSFER_LOG_COST: int = 0 TX_VALUE_COST: int = 0 # Contract Creation diff --git a/src/ethereum/forks/amsterdam/transactions.py b/src/ethereum/forks/amsterdam/transactions.py index ceae8f63168..251c80472e2 100644 --- a/src/ethereum/forks/amsterdam/transactions.py +++ b/src/ethereum/forks/amsterdam/transactions.py @@ -639,8 +639,8 @@ def calculate_intrinsic_cost( call, or `CREATE_ACCESS` for a contract creation). The created account's `NEW_ACCOUNT` state gas is state-dependent and is charged at the top frame, not here. - 3. Value cost (`TRANSFER_LOG_COST`, plus `TX_VALUE_COST` for a - non-self-transfer call) when ``tx.value > 0``. + 3. Value cost (`TX_VALUE_COST` for a non-self-transfer call) when + ``tx.value > 0``. 4. Calldata cost (zero and non-zero bytes). 5. Access list entries (if applicable). 6. Authorizations (if applicable): only the state-independent base @@ -671,14 +671,10 @@ def calculate_intrinsic_cost( if is_create: recipient_regular_gas = GasCosts.CREATE_ACCESS init_code_gas = init_code_cost(ulen(tx.data)) - if tx.value > U256(0): - recipient_regular_gas += GasCosts.TRANSFER_LOG_COST elif not is_self_transfer: recipient_regular_gas = GasCosts.COLD_ACCOUNT_ACCESS if tx.value > U256(0): - recipient_regular_gas += ( - GasCosts.TRANSFER_LOG_COST + GasCosts.TX_VALUE_COST - ) + recipient_regular_gas += GasCosts.TX_VALUE_COST access_list_cost = Uint(0) tokens_in_access_list = Uint(0) diff --git a/src/ethereum/forks/amsterdam/vm/gas.py b/src/ethereum/forks/amsterdam/vm/gas.py index 028b4ac4318..d4fd188946f 100644 --- a/src/ethereum/forks/amsterdam/vm/gas.py +++ b/src/ethereum/forks/amsterdam/vm/gas.py @@ -137,8 +137,7 @@ class GasCosts: # Transactions TX_BASE: Final[Uint] = Uint(12000) TX_CREATE: Final[Uint] = Uint(32000) - TX_VALUE_COST: Final[Uint] = Uint(4244) - TRANSFER_LOG_COST: Final[Uint] = Uint(1756) + TX_VALUE_COST: Final[Uint] = Uint(6000) TX_DATA_TOKEN_STANDARD: Final[Uint] = Uint(4) TX_DATA_TOKEN_FLOOR: Final[Uint] = Uint(16) TX_ACCESS_LIST_ADDRESS: Final[Uint] = COLD_ACCOUNT_ACCESS diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/spec.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/spec.py index 5ac7b7e50bf..33b12ffbd2f 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/spec.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/spec.py @@ -13,5 +13,5 @@ class ReferenceSpec: ref_spec_2780 = ReferenceSpec( git_path="EIPS/eip-2780.md", - version="e6d8f589d355e891c37ff479d3ce668352e5b1be", + version="04dd54c2e7ec1f408cf4a150d5c1aa43573bd025", ) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py index be3c77466ee..b0ba400f25e 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py @@ -259,13 +259,11 @@ def test_calldata_floor_contract_creation( empty code, and prices every byte as one floor token. - ``floor_binds``: ``gas_used`` pins to the floor, which anchors - on the creation regular base (``TX_BASE + CREATE_ACCESS``, plus - ``TRANSFER_LOG_COST`` when value moves) but excludes the created - account's ``NEW_ACCOUNT`` *state* charge and the init-code word - cost -- both masked by the binding floor -- while the deploy - (and any moved wei) still lands. The receipt pins the floor - exactly, so the value-bearing case sits precisely - ``TRANSFER_LOG_COST`` above the zero-value one. + on the creation regular base (``TX_BASE + CREATE_ACCESS``) + but excludes the created account's ``NEW_ACCOUNT`` *state* charge + and the init-code word cost -- both masked by the binding floor -- + while the deploy (and any moved wei) still lands. + The receipt pins the floor exactly. - ``below_floor``: a gas limit one short of the floor still covers the creation intrinsic, so the rejection is pinned to the floor, with ``INTRINSIC_GAS_BELOW_FLOOR_GAS_COST``. diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py index 80b21d0c35a..4f0cffce3c8 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py @@ -102,9 +102,7 @@ def test_intrinsic_reduction_across_amsterdam_transition( if not self_transfer: expected_post += post_gas_costs.COLD_ACCOUNT_ACCESS if value: - expected_post += ( - post_gas_costs.TRANSFER_LOG_COST + post_gas_costs.TX_VALUE_COST - ) + expected_post += post_gas_costs.TX_VALUE_COST timestamps = [PRE_FORK_TIMESTAMP, POST_FORK_TIMESTAMP] expected_intrinsics = [expected_pre, expected_post] @@ -181,9 +179,8 @@ def test_creation_tx_intrinsic_across_amsterdam_transition( block, each from a fresh sender with the gas limit pinned exactly. Pre-fork the whole cost is regular intrinsic: ``TX_BASE`` plus the flat ``TX_CREATE``. Post-fork the intrinsic keeps only the - ``CREATE_ACCESS`` regular portion of ``TX_CREATE`` (plus the - transfer-log charge when value moves), while the created account's - ``NEW_ACCOUNT`` is charged as *state* gas at the top frame — the + ``CREATE_ACCESS`` regular portion of ``TX_CREATE``, while the created + account's ``NEW_ACCOUNT`` is charged as *state* gas at the top frame — the sender-facing total is the sum of both. The per-fork costs are hand-derived from each fork's gas constants @@ -217,8 +214,6 @@ def test_creation_tx_intrinsic_across_amsterdam_transition( + (post_costs.TX_CREATE - post_costs.NEW_ACCOUNT) + init_code_terms ) - if value: - expected_post += post_costs.TRANSFER_LOG_COST expected_post_state = post_costs.NEW_ACCOUNT timestamps = [PRE_FORK_TIMESTAMP, POST_FORK_TIMESTAMP] diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py index a329470b6ba..42d694f0c2f 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py @@ -67,11 +67,10 @@ def test_value_moving_transactions( ``NEW_ACCOUNT`` state charge when value is transferred. The EIP-7708 transfer log is asserted to fire exactly when - ``TRANSFER_LOG_COST`` is charged: for a non-self value transfer, + ``TX_VALUE_COST`` is charged: for a non-self value transfer, and never for a self-transfer (carve-out) or a zero-value tx. """ - sender_initial_balance = 10**18 - sender = pre.fund_eoa(sender_initial_balance) + sender = pre.fund_eoa() target = setup_target(pre, recipient_type, sender) target_initial_balance = ( @@ -95,13 +94,12 @@ def test_value_moving_transactions( # spills entirely into regular gas. total_gas_cost = intrinsic_gas + top_frame_gas + top_frame_state_gas - tx_gas_limit = total_gas_cost + 1000 # add a small buffer - gas_price = 1_000_000_000 + tx_gas_limit = total_gas_cost is_self_transfer = recipient_type == RecipientType.SELF # A transfer log is emitted iff value moves to a distinct account, - # which is exactly when the intrinsic includes ``TRANSFER_LOG_COST``. + # which is exactly when the intrinsic includes ``TX_VALUE_COST``. # ``logs=[]`` asserts no log fires for the carved-out cases. if value > 0 and not is_self_transfer: expected_logs = [transfer_log(sender, target, value)] @@ -113,19 +111,13 @@ def test_value_moving_transactions( to=target, value=value, gas_limit=tx_gas_limit, - gas_price=gas_price, - expected_receipt=TransactionReceipt(logs=expected_logs), - ) - - sender_value_delta = 0 if is_self_transfer else value - sender_final_balance = ( - sender_initial_balance - - sender_value_delta - - total_gas_cost * gas_price + expected_receipt=TransactionReceipt( + cumulative_gas_used=tx_gas_limit, logs=expected_logs + ), ) post: dict[Address, Account | None] = { - sender: Account(nonce=1, balance=sender_final_balance), + sender: Account(nonce=1), } if not is_self_transfer: if recipient_type == RecipientType.EMPTY_ACCOUNT and value == 0: