Skip to content

Commit fb17a68

Browse files
fselmomarioevz
andauthored
feat(spec,test): EIP-7708 spec updates for self as target (#2086)
* fix(spec,text): Updates to EIP-7708 spec for bal-devnet-2 - fix(spec,test): EIP-7708 emit selfdestruct logs only in same tx - fix(spec,test): EIP-7708, only emit on transfers to other accounts - Add more tests that match these edge cases * feat(test): tests for finalization selfdest + bal transfer in lexicographic order * fix: changes from comments on PR #2086 * add more variants to test_selfdestruct_same_tx_via_call * fix: docstring * refactor: improvements from comments on PR #2086 * Update test to use dynamic addresses --------- Co-authored-by: Mario Vega <marioevz@gmail.com>
1 parent 6b9199e commit fb17a68

6 files changed

Lines changed: 689 additions & 63 deletions

File tree

src/ethereum/forks/amsterdam/vm/instructions/system.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -684,11 +684,12 @@ def selfdestruct(evm: Evm) -> None:
684684
beneficiary_new_balance,
685685
)
686686

687-
# EIP-7708: Emit appropriate log based on beneficiary
688-
if beneficiary == originator:
689-
# Self-destruct to self burns the balance
687+
# EIP-7708: Emit appropriate log based on whether ETH is burned
688+
# or transferred to a different account
689+
if originator in state.created_accounts and beneficiary == originator:
690+
# Self-destruct to self in same tx burns the balance
690691
emit_selfdestruct_log(evm, originator, originator_balance)
691-
else:
692+
elif beneficiary != originator:
692693
# Transfer to different beneficiary
693694
emit_transfer_log(evm, originator, beneficiary, originator_balance)
694695

src/ethereum/forks/amsterdam/vm/interpreter.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -314,9 +314,11 @@ def process_message(message: Message) -> Evm:
314314
move_ether(
315315
state, message.caller, message.current_target, message.value
316316
)
317-
emit_transfer_log(
318-
evm, message.caller, message.current_target, message.value
319-
)
317+
# EIP-7708: Only emit transfer log to a different account
318+
if message.caller != message.current_target:
319+
emit_transfer_log(
320+
evm, message.caller, message.current_target, message.value
321+
)
320322

321323
sender_new_balance = get_account(state, message.caller).balance
322324
recipient_new_balance = get_account(

tests/amsterdam/eip7708_eth_transfer_logs/spec.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class ReferenceSpec:
1414

1515

1616
ref_spec_7708 = ReferenceSpec(
17-
"EIPS/eip-7708.md", "a7c5b2ff5697d5a0be5ea804a89d98a7fd0dce60"
17+
"EIPS/eip-7708.md", "172188d7b090ed1afb876140f45e19ac00cba4bb"
1818
)
1919

2020

tests/amsterdam/eip7708_eth_transfer_logs/test_fork_transition.py

Lines changed: 93 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
Op,
1515
Transaction,
1616
TransactionReceipt,
17+
compute_create_address,
1718
)
1819

1920
from .spec import ref_spec_7708, selfdestruct_log, transfer_log
@@ -22,68 +23,116 @@
2223
REFERENCE_SPEC_VERSION = ref_spec_7708.version
2324

2425

26+
@pytest.mark.parametrize(
27+
"same_tx,to_self",
28+
[
29+
pytest.param(True, True, id="same_tx_to_self"),
30+
pytest.param(False, True, id="pre_existing_to_self"),
31+
pytest.param(False, False, id="pre_existing_to_other"),
32+
],
33+
)
2534
@pytest.mark.valid_at_transition_to("Amsterdam")
2635
def test_selfdestruct_log_at_fork_transition(
27-
blockchain_test: BlockchainTestFiller, pre: Alloc
36+
blockchain_test: BlockchainTestFiller,
37+
pre: Alloc,
38+
same_tx: bool,
39+
to_self: bool,
2840
) -> None:
2941
"""
30-
Test ETH selfdestruct log behavior at fork transition.
42+
Test selfdestruct log emission across the Amsterdam fork transition.
43+
44+
same_tx_to_self: Factory CREATEs and selfdestructs to self in one tx.
45+
At/after Amsterdam emits a CREATE transfer log + Selfdestruct log.
3146
32-
Before Amsterdam: ETH selfdestructs do NOT emit logs.
33-
At/after Amsterdam: ETH selfdestructs emit Selfdestruct logs.
47+
pre_existing_to_self: Pre-existing contract selfdestructs to self.
48+
No logs at any fork — SELFDESTRUCT to same account emits nothing.
49+
50+
pre_existing_to_other: Pre-existing contract selfdestructs to a different
51+
account. At/after Amsterdam emits a Transfer log.
3452
"""
3553
sender = pre.fund_eoa()
36-
contract1 = pre.deploy_contract(Op.SELFDESTRUCT(Op.ADDRESS), balance=1)
37-
contract2 = pre.deploy_contract(Op.SELFDESTRUCT(Op.ADDRESS), balance=2)
38-
contract3 = pre.deploy_contract(Op.SELFDESTRUCT(Op.ADDRESS), balance=3)
54+
contract_balance = 1000
3955

40-
blocks = [
41-
Block(
42-
timestamp=14_999,
43-
txs=[
44-
Transaction(
45-
to=contract1,
46-
sender=sender,
47-
gas_limit=100_000,
48-
expected_receipt=TransactionReceipt(logs=[]),
49-
)
56+
if same_tx:
57+
initcode = Op.SELFDESTRUCT(Op.ADDRESS)
58+
initcode_bytes = bytes(initcode)
59+
initcode_len = len(initcode_bytes)
60+
61+
factory_code = Op.MSTORE(
62+
0, Op.PUSH32(initcode_bytes.rjust(32, b"\x00"))
63+
) + Op.CREATE(
64+
value=contract_balance, offset=32 - initcode_len, size=initcode_len
65+
)
66+
67+
factory = pre.deploy_contract(
68+
factory_code, balance=contract_balance * 3
69+
)
70+
created = [
71+
compute_create_address(address=factory, nonce=n)
72+
for n in range(1, 4)
73+
]
74+
targets = [factory] * 3
75+
76+
expected_logs = [
77+
[],
78+
[
79+
transfer_log(factory, created[1], contract_balance),
80+
selfdestruct_log(created[1], contract_balance),
5081
],
51-
),
52-
Block(
53-
timestamp=15_000,
54-
txs=[
55-
Transaction(
56-
to=contract2,
57-
sender=sender,
58-
gas_limit=100_000,
59-
expected_receipt=TransactionReceipt(
60-
logs=[selfdestruct_log(contract2, 2)]
61-
),
62-
)
82+
[
83+
transfer_log(factory, created[2], contract_balance),
84+
selfdestruct_log(created[2], contract_balance),
6385
],
64-
),
86+
]
87+
post: dict = {
88+
sender: Account(nonce=3),
89+
created[0]: Account.NONEXISTENT,
90+
created[1]: Account.NONEXISTENT,
91+
created[2]: Account.NONEXISTENT,
92+
}
93+
elif to_self:
94+
targets = [
95+
pre.deploy_contract(
96+
Op.SELFDESTRUCT(Op.ADDRESS), balance=contract_balance
97+
)
98+
for _ in range(3)
99+
]
100+
expected_logs = [[], [], []]
101+
post = {sender: Account(nonce=3)}
102+
else:
103+
beneficiary = pre.empty_account()
104+
targets = [
105+
pre.deploy_contract(
106+
Op.SELFDESTRUCT(beneficiary), balance=contract_balance
107+
)
108+
for _ in range(3)
109+
]
110+
expected_logs = [
111+
[],
112+
[transfer_log(targets[1], beneficiary, contract_balance)],
113+
[transfer_log(targets[2], beneficiary, contract_balance)],
114+
]
115+
post = {
116+
sender: Account(nonce=3),
117+
beneficiary: Account(balance=contract_balance * 3),
118+
}
119+
120+
blocks = [
65121
Block(
66-
timestamp=15_001,
122+
timestamp=ts,
67123
txs=[
68124
Transaction(
69-
to=contract3,
125+
to=targets[i],
70126
sender=sender,
71-
gas_limit=100_000,
72-
expected_receipt=TransactionReceipt(
73-
logs=[selfdestruct_log(contract3, 3)]
74-
),
127+
gas_limit=200_000,
128+
expected_receipt=TransactionReceipt(logs=expected_logs[i]),
75129
)
76130
],
77-
),
131+
)
132+
for i, ts in enumerate([14_999, 15_000, 15_001])
78133
]
79134

80-
blockchain_test(
81-
pre=pre,
82-
blocks=blocks,
83-
post={
84-
sender: Account(nonce=3),
85-
},
86-
)
135+
blockchain_test(pre=pre, blocks=blocks, post=post)
87136

88137

89138
@pytest.mark.valid_at_transition_to("Amsterdam")

0 commit comments

Comments
 (0)