Skip to content

Commit 75a31d9

Browse files
committed
Merge remote-tracking branch 'upstream/eips/amsterdam/eip-7708' into devnets/bal/2
2 parents 018f548 + 3eb3161 commit 75a31d9

2 files changed

Lines changed: 195 additions & 26 deletions

File tree

src/ethereum/forks/amsterdam/fork.py

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,9 +1056,23 @@ def process_transaction(
10561056
sender_balance_after_refund,
10571057
)
10581058

1059-
# EIP-7708: Emit selfdestruct logs for remaining balance at finalization.
1060-
# This handles the case where a contract receives ETH after being flagged
1061-
# for SELFDESTRUCT but before finalization.
1059+
# transfer miner fees
1060+
coinbase_balance_after_mining_fee = get_account(
1061+
block_env.state, block_env.coinbase
1062+
).balance + U256(transaction_fee)
1063+
set_account_balance(
1064+
block_env.state,
1065+
block_env.coinbase,
1066+
coinbase_balance_after_mining_fee,
1067+
)
1068+
track_balance_change(
1069+
tx_env.state_changes,
1070+
block_env.coinbase,
1071+
coinbase_balance_after_mining_fee,
1072+
)
1073+
1074+
# EIP-7708: Emit burn logs for balances held by accounts marked for
1075+
# deletion AFTER miner fee transfer.
10621076
finalization_logs: List[Log] = []
10631077
for address in sorted(tx_output.accounts_to_delete):
10641078
balance = get_account(block_env.state, address).balance
@@ -1077,19 +1091,6 @@ def process_transaction(
10771091

10781092
all_logs = tx_output.logs + tuple(finalization_logs)
10791093

1080-
coinbase_balance_after_mining_fee = get_account(
1081-
block_env.state, block_env.coinbase
1082-
).balance + U256(transaction_fee)
1083-
1084-
set_account_balance(
1085-
block_env.state, block_env.coinbase, coinbase_balance_after_mining_fee
1086-
)
1087-
track_balance_change(
1088-
tx_env.state_changes,
1089-
block_env.coinbase,
1090-
coinbase_balance_after_mining_fee,
1091-
)
1092-
10931094
if coinbase_balance_after_mining_fee == 0 and account_exists_and_is_empty(
10941095
block_env.state, block_env.coinbase
10951096
):

tests/amsterdam/eip7708_eth_transfer_logs/test_selfdestruct_logs.py

Lines changed: 178 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@
1212
Account,
1313
Address,
1414
Alloc,
15+
Block,
16+
BlockchainTestFiller,
1517
Bytecode,
1618
Environment,
19+
Fork,
20+
Header,
1721
Initcode,
1822
Op,
1923
Opcodes,
@@ -74,12 +78,14 @@ def test_selfdestruct_to_self_pre_existing_no_log(
7478
pytest.param(0, id="zero_balance"),
7579
],
7680
)
81+
@pytest.mark.with_all_create_opcodes
7782
def test_selfdestruct_to_self_same_tx(
7883
state_test: StateTestFiller,
7984
env: Environment,
8085
pre: Alloc,
8186
sender: EOA,
8287
contract_balance: int,
88+
create_opcode: Op,
8389
) -> None:
8490
"""
8591
Test selfdestruct-to-self for same-tx created contracts.
@@ -93,12 +99,18 @@ def test_selfdestruct_to_self_same_tx(
9399

94100
factory_code = Op.MSTORE(
95101
0, Op.PUSH32(initcode_bytes.rjust(32, b"\x00"))
96-
) + Op.CREATE(
102+
) + create_opcode(
97103
value=Op.CALLVALUE, offset=32 - initcode_len, size=initcode_len
98104
)
99105

100106
factory = pre.deploy_contract(factory_code)
101-
created_address = compute_create_address(address=factory, nonce=1)
107+
created_address = compute_create_address(
108+
address=factory,
109+
nonce=1,
110+
salt=0,
111+
initcode=initcode_bytes,
112+
opcode=create_opcode,
113+
)
102114

103115
if contract_balance > 0:
104116
expected_logs = [
@@ -127,12 +139,14 @@ def test_selfdestruct_to_self_same_tx(
127139
pytest.param(0, id="zero_balance"),
128140
],
129141
)
142+
@pytest.mark.with_all_create_opcodes
130143
def test_selfdestruct_to_different_address_same_tx(
131144
state_test: StateTestFiller,
132145
env: Environment,
133146
pre: Alloc,
134147
sender: EOA,
135148
contract_balance: int,
149+
create_opcode: Op,
136150
) -> None:
137151
"""
138152
Test same-tx selfdestruct to different address.
@@ -147,12 +161,18 @@ def test_selfdestruct_to_different_address_same_tx(
147161

148162
factory_code = Op.MSTORE(
149163
0, Op.PUSH32(initcode_bytes.rjust(32, b"\x00"))
150-
) + Op.CREATE(
164+
) + create_opcode(
151165
value=Op.CALLVALUE, offset=32 - initcode_len, size=initcode_len
152166
)
153167

154168
factory = pre.deploy_contract(factory_code)
155-
created_address = compute_create_address(address=factory, nonce=1)
169+
created_address = compute_create_address(
170+
address=factory,
171+
nonce=1,
172+
salt=0,
173+
initcode=initcode_bytes,
174+
opcode=create_opcode,
175+
)
156176

157177
if contract_balance > 0:
158178
expected_logs = [
@@ -364,11 +384,7 @@ def test_finalization_selfdestruct_logs(
364384
reverse_sorted = list(reversed(sorted_addrs))
365385

366386
# Runtime: selfdestruct on first call, STOP on subsequent calls
367-
selfdestruct_target: Address | Opcodes
368-
if to_self:
369-
selfdestruct_target = Op.ADDRESS
370-
else:
371-
selfdestruct_target = beneficiary
387+
target: Address | Opcodes = Op.ADDRESS if to_self else beneficiary
372388
runtime = (
373389
Op.TLOAD(0)
374390
+ Op.ISZERO
@@ -377,7 +393,7 @@ def test_finalization_selfdestruct_logs(
377393
+ Op.STOP
378394
+ Op.JUMPDEST
379395
+ Op.TSTORE(0, 1)
380-
+ Op.SELFDESTRUCT(selfdestruct_target)
396+
+ Op.SELFDESTRUCT(target)
381397
)
382398
initcode = Initcode(deploy_code=runtime)
383399
initcode_len = len(initcode)
@@ -497,3 +513,155 @@ def test_finalization_selfdestruct_logs(
497513
)
498514

499515
state_test(env=env, pre=pre, post=post, tx=tx)
516+
517+
518+
@pytest.mark.parametrize(
519+
"funded_after_selfdestruct",
520+
[
521+
pytest.param(True, id="funded_after_selfdestruct"),
522+
pytest.param(False, id="miner_fee_only"),
523+
],
524+
)
525+
def test_selfdestruct_finalization_after_priority_fee(
526+
blockchain_test: BlockchainTestFiller,
527+
pre: Alloc,
528+
fork: Fork,
529+
funded_after_selfdestruct: bool,
530+
) -> None:
531+
"""
532+
Verify finalization burn logs are emitted after priority fee payment.
533+
534+
Sets coinbase to a contract that self-destructs in the same tx. The
535+
finalization burn log includes the priority fee, proving finalization
536+
happens after fee payment per EIP-7708.
537+
538+
funded_after_selfdestruct:
539+
- if True: payer sends ETH, finalization = funding + priority_fee
540+
- if False: no payer, finalization = priority_fee only
541+
"""
542+
contract_balance = 1000
543+
funding_amount = 10_000 if funded_after_selfdestruct else 0
544+
545+
sender = pre.fund_eoa()
546+
547+
factory_address = compute_create_address(address=sender, nonce=0)
548+
created_address = compute_create_address(address=factory_address, nonce=1)
549+
coinbase = created_address # coinbase == self-destructed contract
550+
551+
# inner contract: simple SELFDESTRUCT to self
552+
runtime_code = Op.SELFDESTRUCT(Op.ADDRESS)
553+
initcode = Initcode(deploy_code=runtime_code)
554+
initcode_len = len(initcode)
555+
556+
gas_costs = fork.gas_costs()
557+
mem_after_mstore = ((initcode_len + 31) // 32) * 32
558+
559+
# The base factory code: CREATE + CALL to trigger selfdestruct
560+
factory_code = Om.MSTORE(
561+
initcode, 0, new_memory_size=mem_after_mstore
562+
) + Op.CALL(
563+
gas=100_000,
564+
address=Op.CREATE(
565+
value=contract_balance,
566+
offset=0,
567+
size=initcode_len,
568+
init_code_size=initcode_len,
569+
),
570+
address_warm=True,
571+
)
572+
573+
# optionally add payer call to fund coinbase after selfdestruct
574+
payer = None
575+
payer_runtime_gas = 0
576+
if funded_after_selfdestruct:
577+
payer_code = Op.SELFDESTRUCT(Op.CALLDATALOAD(0))
578+
payer = pre.deploy_contract(payer_code, balance=funding_amount)
579+
factory_code += Op.MSTORE(0, created_address)
580+
factory_code += Op.CALL(
581+
gas=100_000, address=payer, args_offset=0, args_size=32
582+
)
583+
payer_runtime_gas = Op.SELFDESTRUCT(
584+
Op.CALLDATALOAD(0), address_warm=True, account_new=False
585+
).gas_cost(fork)
586+
587+
pre.fund_address(factory_address, contract_balance)
588+
589+
# prio fee calc
590+
genesis_base_fee = 7
591+
gas_price = 10
592+
base_fee = fork.base_fee_per_gas_calculator()(
593+
parent_base_fee_per_gas=genesis_base_fee,
594+
parent_gas_used=0,
595+
parent_gas_limit=Environment().gas_limit,
596+
)
597+
priority_fee_per_gas = gas_price - base_fee
598+
599+
intrinsic_gas = fork.transaction_intrinsic_cost_calculator()(
600+
calldata=bytes(factory_code),
601+
contract_creation=True,
602+
)
603+
factory_gas = factory_code.gas_cost(fork)
604+
initcode_exec_gas = initcode.execution_gas
605+
code_deposit_gas = len(runtime_code) * gas_costs.G_CODE_DEPOSIT_BYTE
606+
inner_runtime_gas = Op.SELFDESTRUCT(
607+
Op.ADDRESS, address_warm=True, account_new=False
608+
).gas_cost(fork)
609+
610+
gas_used = (
611+
intrinsic_gas
612+
+ factory_gas
613+
+ initcode_exec_gas
614+
+ code_deposit_gas
615+
+ inner_runtime_gas
616+
+ payer_runtime_gas
617+
)
618+
priority_fee = priority_fee_per_gas * gas_used
619+
620+
# Finalization burn log proves coinbase received priority fee before log
621+
finalization_balance = funding_amount + priority_fee
622+
623+
expected_logs = [
624+
transfer_log(factory_address, created_address, contract_balance),
625+
selfdestruct_log(created_address, contract_balance),
626+
]
627+
628+
# if funded after selfdestruct, expect transfer log from payer
629+
if funded_after_selfdestruct:
630+
assert payer is not None
631+
expected_logs.append(
632+
transfer_log(payer, created_address, funding_amount)
633+
)
634+
635+
# finalization selfdestruct log
636+
expected_logs.append(
637+
selfdestruct_log(created_address, finalization_balance)
638+
)
639+
640+
tx = Transaction(
641+
sender=sender,
642+
to=None,
643+
value=0,
644+
data=factory_code,
645+
gas_limit=500_000,
646+
gas_price=gas_price,
647+
expected_receipt=TransactionReceipt(logs=expected_logs),
648+
)
649+
650+
post: dict[Address, Account | None] = {
651+
created_address: Account.NONEXISTENT,
652+
}
653+
if payer is not None:
654+
post[payer] = Account(balance=0)
655+
656+
blockchain_test(
657+
pre=pre,
658+
blocks=[
659+
Block(
660+
txs=[tx],
661+
fee_recipient=coinbase,
662+
header_verify=Header(base_fee_per_gas=base_fee),
663+
)
664+
],
665+
post=post,
666+
genesis_environment=Environment(base_fee_per_gas=genesis_base_fee),
667+
)

0 commit comments

Comments
 (0)