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
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ addopts =
--ignore tests/cancun/eip4844_blobs/point_evaluation_vectors/
--ignore tests/json_loader
--ignore tests/evm_tools
--ignore tests/ported_static
# these customizations require the pytest-custom-report plugin
report_passed_verbose = FILLED
report_xpassed_verbose = XFILLED
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from execution_testing.base_types import AccessList
from execution_testing.base_types.conversions import BytesConvertible
from execution_testing.vm import OpcodeBase
from execution_testing.vm import OpcodeBase, Opcodes

from .....recipient_type import RecipientType
from ....base_fork import (
Expand All @@ -37,6 +37,7 @@ def gas_costs(cls) -> GasCosts:
COLD_ACCOUNT_COST_CODE=2_600,
COLD_ACCOUNT_COST_NO_CODE=500,
STATE_UPDATE=1_000,
TRANSFER_LOG_COST=1_756,
)

@classmethod
Expand Down Expand Up @@ -78,6 +79,7 @@ def fn(
" RecipientType.DELEGATION_7702"
)

log_cost = 0
if contract_creation or recipient_type == RecipientType.SELF:
access_cost = 0
update_cost = 0
Expand All @@ -86,6 +88,7 @@ def fn(
update_cost = 0
if sends_value:
update_cost += gas_costs.STATE_UPDATE
log_cost = gas_costs.TRANSFER_LOG_COST
else:
if recipient_is_warm:
access_cost = gas_costs.WARM_ACCESS
Expand All @@ -109,8 +112,9 @@ def fn(
update_cost = gas_costs.NEW_ACCOUNT
else:
update_cost = gas_costs.STATE_UPDATE
log_cost = gas_costs.TRANSFER_LOG_COST

intrinsic_cost += access_cost + update_cost
intrinsic_cost += access_cost + update_cost + log_cost

if return_cost_deducted_prior_execution:
return intrinsic_cost
Expand Down Expand Up @@ -138,8 +142,18 @@ def _calculate_call_gas(
- ``COLD_ACCOUNT_COST_CODE`` (2600) for targets with code

Value transfer replaces ``CALL_VALUE`` (9000) with:
- ``2 * STATE_UPDATE`` (2000) for non-empty targets
- ``STATE_UPDATE + NEW_ACCOUNT`` (26000) for empty targets
- ``STATE_UPDATE`` (1000) for self-calls (``caller == to``); no
``TRANSFER_LOG_COST`` applies since EIP-7708 does not emit a log
for self-transfers. ``CALLCODE`` is always a self-call because
it runs target code in the caller's own context.
- ``2 * STATE_UPDATE + TRANSFER_LOG_COST`` (3756) for existing
non-self targets.
- ``STATE_UPDATE + NEW_ACCOUNT + TRANSFER_LOG_COST`` (27756) for
empty non-self targets.

Self-call scenarios for ``CALL`` are indicated by the
``self_call`` metadata flag; it defaults to ``False`` so existing
non-self tests remain unaffected.
"""
metadata = opcode.metadata

Expand All @@ -152,10 +166,21 @@ def _calculate_call_gas(

value_cost = 0
if "value_transfer" in metadata and metadata["value_transfer"]:
if metadata["account_new"]:
value_cost = gas_costs.STATE_UPDATE + gas_costs.NEW_ACCOUNT
is_self_call = opcode == Opcodes.CALLCODE or metadata.get(
"self_call", False
)
if is_self_call:
value_cost = gas_costs.STATE_UPDATE
elif metadata["account_new"]:
value_cost = (
gas_costs.STATE_UPDATE
+ gas_costs.NEW_ACCOUNT
+ gas_costs.TRANSFER_LOG_COST
)
else:
value_cost = 2 * gas_costs.STATE_UPDATE
value_cost = (
2 * gas_costs.STATE_UPDATE + gas_costs.TRANSFER_LOG_COST
)

delegation_cost = 0
if metadata["delegated_address"] or metadata["delegated_address_warm"]:
Expand All @@ -166,6 +191,24 @@ def _calculate_call_gas(

return access_cost + value_cost + delegation_cost

@classmethod
def _calculate_selfdestruct_gas(
cls, opcode: OpcodeBase, gas_costs: GasCosts
) -> int:
"""
SELFDESTRUCT adds ``TRANSFER_LOG_COST`` when the destruction
moves non-zero balance to a different beneficiary, mirroring the
runtime rule in EIP-2780.
"""
base_cost = super(EIP2780, cls)._calculate_selfdestruct_gas(
opcode, gas_costs
)

if opcode.metadata.get("transfers_value", False):
base_cost += gas_costs.TRANSFER_LOG_COST

return base_cost

@classmethod
def _with_account_access(
cls,
Expand Down
1 change: 1 addition & 0 deletions packages/testing/src/execution_testing/forks/gas_costs.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,4 @@ class GasCosts:
COLD_ACCOUNT_COST_CODE: int = 0
COLD_ACCOUNT_COST_NO_CODE: int = 0
STATE_UPDATE: int = 0
TRANSFER_LOG_COST: int = 0
12 changes: 11 additions & 1 deletion packages/testing/src/execution_testing/vm/opcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5165,6 +5165,7 @@ class Opcodes(Opcode, Enum):
"address_has_code": True,
"value_transfer": False,
"account_new": False,
"self_call": False,
"new_memory_size": 0,
"old_memory_size": 0,
"delegated_address": False,
Expand Down Expand Up @@ -5216,6 +5217,8 @@ class Opcodes(Opcode, Enum):
- address_warm: whether the address is already warm (default: False)
- value_transfer: whether value is being transferred (default: False)
- account_new: whether creating a new account (default: False)
- self_call: whether ``caller == to`` so the value transfer is a
self-transfer (default: False); consumed by EIP-2780
- new_memory_size: memory size after expansion in bytes (default: 0)
- old_memory_size: memory size before expansion in bytes (default: 0)
- delegated_address: whether the target is a delegated account
Expand Down Expand Up @@ -5631,7 +5634,11 @@ class Opcodes(Opcode, Enum):
0xFF,
popped_stack_items=1,
kwargs=["address"],
metadata={"address_warm": False, "account_new": False},
metadata={
"address_warm": False,
"account_new": False,
"transfers_value": False,
},
)
"""
SELFDESTRUCT(address)
Expand Down Expand Up @@ -5659,6 +5666,9 @@ class Opcodes(Opcode, Enum):
(default: False)
- account_new: whether creating a new beneficiary account, requires
non-zero balance in the source account (default: False)
- transfers_value: whether the destruction moves non-zero balance to
a different beneficiary (default: False); consumed
by EIP-2780 to charge ``TRANSFER_LOG_COST``

Source: [evm.codes/#FF](https://www.evm.codes/#FF)
"""
Expand Down
5 changes: 4 additions & 1 deletion src/ethereum/forks/amsterdam/fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -1022,7 +1022,9 @@ def calculate_recipient_gas_cost(

if tx.to in PRE_COMPILED_CONTRACTS:
if tx.value > U256(0):
tx_recipient_cost += GasCosts.STATE_UPDATE
tx_recipient_cost += (
GasCosts.STATE_UPDATE + GasCosts.TRANSFER_LOG_COST
)
return tx_recipient_cost

is_cold_access = tx.to not in access_list_addresses
Expand Down Expand Up @@ -1056,6 +1058,7 @@ def calculate_recipient_gas_cost(
tx_recipient_cost += GasCosts.NEW_ACCOUNT
else:
tx_recipient_cost += GasCosts.STATE_UPDATE
tx_recipient_cost += GasCosts.TRANSFER_LOG_COST

return tx_recipient_cost

Expand Down
9 changes: 9 additions & 0 deletions src/ethereum/forks/amsterdam/vm/gas.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ class GasCosts:

[EIP-2780]: https://eips.ethereum.org/EIPS/eip-2780
"""
TRANSFER_LOG_COST = Uint(1756)
"""
Gas cost for the [EIP-7708] transfer log (LOG3 equivalent:
`375 + 3*375 + 32*8`) charged on every nonzero-value transfer to a
different account. Introduced by [EIP-2780].

[EIP-2780]: https://eips.ethereum.org/EIPS/eip-2780
[EIP-7708]: https://eips.ethereum.org/EIPS/eip-7708
"""

# Contract Creation
CODE_DEPOSIT_PER_BYTE = Uint(200)
Expand Down
38 changes: 24 additions & 14 deletions src/ethereum/forks/amsterdam/vm/instructions/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,10 +402,18 @@ def call(evm: Evm) -> None:

call_value_cost = Uint(0)
if value > U256(0):
if call_target == EMPTY_ACCOUNT:
call_value_cost += GasCosts.STATE_UPDATE + GasCosts.NEW_ACCOUNT
if evm.message.current_target == to:
call_value_cost += GasCosts.STATE_UPDATE
elif call_target == EMPTY_ACCOUNT:
call_value_cost += (
GasCosts.STATE_UPDATE
+ GasCosts.NEW_ACCOUNT
+ GasCosts.TRANSFER_LOG_COST
)
else:
call_value_cost += Uint(2) * GasCosts.STATE_UPDATE
call_value_cost += (
Uint(2) * GasCosts.STATE_UPDATE + GasCosts.TRANSFER_LOG_COST
)

extra_gas = access_gas_cost + call_value_cost
(
Expand Down Expand Up @@ -496,10 +504,10 @@ def callcode(evm: Evm) -> None:
if is_cold_access:
access_gas_cost = GasCosts.COLD_ACCOUNT_COST_NO_CODE

# Cost is simply dependent on value since the contract is always there
call_value_cost = (
Uint(0) if value == 0 else Uint(2) * GasCosts.STATE_UPDATE
)
# CALLCODE executes the target's code in the caller's own context, so
# the value transfer is from the caller to itself. Only one
# STATE_UPDATE is charged and no TRANSFER_LOG_COST applies.
call_value_cost = Uint(0) if value == 0 else GasCosts.STATE_UPDATE

# check static gas before state access
check_gas(evm, access_gas_cost + extend_memory.cost + call_value_cost)
Expand Down Expand Up @@ -600,16 +608,18 @@ def selfdestruct(evm: Evm) -> None:
if is_cold_access:
evm.accessed_addresses.add(beneficiary)

if (
not is_account_alive(tx_state, beneficiary)
and get_account(tx_state, evm.message.current_target).balance != 0
):
originator = evm.message.current_target
originator_balance = get_account(tx_state, originator).balance

if not is_account_alive(
tx_state, beneficiary
) and originator_balance > U256(0):
gas_cost += GasCosts.OPCODE_SELFDESTRUCT_NEW_ACCOUNT

charge_gas(evm, gas_cost)
if originator != beneficiary and originator_balance > U256(0):
gas_cost += GasCosts.TRANSFER_LOG_COST

originator = evm.message.current_target
originator_balance = get_account(tx_state, originator).balance
charge_gas(evm, gas_cost)

# Transfer balance
move_ether(tx_state, originator, beneficiary, originator_balance)
Expand Down
2 changes: 1 addition & 1 deletion tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ class ReferenceSpec:

ref_spec_2780 = ReferenceSpec(
git_path="EIPS/eip-2780.md",
version="5c092808affade87ad04086b8ce0c41cb8d2b5dd",
version="c550387e917485af69a6999aea45270a555e2eb7",
)
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ def test_value_move_to_precompiles(
Ensure value moving transactions to precompiles charge gas correctly.

Under EIP-2780, precompile recipients have zero access cost (they are
always warm). Value transfer to a precompile incurs only G_STATE_UPDATE.
always warm). Value transfer to a precompile incurs a
``G_STATE_UPDATE`` plus the EIP-7708 ``TRANSFER_LOG_COST``.
"""
sender_initial_balance = 10**18
sender = pre.fund_eoa(sender_initial_balance)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def _run_call_test(
has_value_transfer: bool,
account_new: bool,
post_fn: PostFn,
is_self_call: bool = False,
) -> None:
"""
Core logic shared by all CALL-family opcode tests.
Expand Down Expand Up @@ -108,13 +109,20 @@ def _run_call_test(
)
bytecode_cost = gsc.VERY_LOW * n_args

# Value cost depends on whether the target is new or existing.
# Value cost depends on whether the transfer is a self-call and on
# whether the target is new or existing. Self-calls charge only a
# single STATE_UPDATE with no TRANSFER_LOG_COST (EIP-7708 does not
# emit a log for self-transfers).
value_cost = 0
if has_value_transfer and value > 0:
if account_new:
value_cost = gsc.STATE_UPDATE + gsc.NEW_ACCOUNT
if is_self_call:
value_cost = gsc.STATE_UPDATE
elif account_new:
value_cost = (
gsc.STATE_UPDATE + gsc.NEW_ACCOUNT + gsc.TRANSFER_LOG_COST
)
else:
value_cost = 2 * gsc.STATE_UPDATE
value_cost = 2 * gsc.STATE_UPDATE + gsc.TRANSFER_LOG_COST

# Gas for the tested threshold, minus 1 for OOG.
scenario_gas = compute_scenario_gas(access, gsc)
Expand Down Expand Up @@ -185,9 +193,10 @@ def test_call(
"""
Test CALL opcode gas charging under EIP-2780.

CALL transfers value from caller to target. With value > 0,
the value cost is 2 * STATE_UPDATE for existing targets
or STATE_UPDATE + NEW_ACCOUNT for new accounts.
CALL transfers value from caller to target. With value > 0, the
value cost is ``2 * STATE_UPDATE + TRANSFER_LOG_COST`` for existing
targets or ``STATE_UPDATE + NEW_ACCOUNT + TRANSFER_LOG_COST`` for
new accounts.
"""
if account_new and access == AccessScenario.COLD_CODE:
pytest.skip("Empty target has no code")
Expand Down Expand Up @@ -277,8 +286,10 @@ def test_callcode(
"""
Test CALLCODE opcode gas charging under EIP-2780.

CALLCODE transfers value to self (caller), so there is no
net balance change even on success with value > 0.
CALLCODE transfers value to self (caller), so there is no net
balance change even on success with value > 0. The value cost is
a single ``STATE_UPDATE`` with no ``TRANSFER_LOG_COST`` because
EIP-7708 does not emit a log for self-transfers.
"""

def caller_code_fn(target: Address, val: int) -> Bytecode:
Expand Down Expand Up @@ -316,6 +327,7 @@ def post_fn(
has_value_transfer=True,
account_new=False,
post_fn=post_fn,
is_self_call=True,
)


Expand Down
Loading