Skip to content

Commit dfbcc10

Browse files
committed
refactor(tests): move selfdestruct bal tests to oog file; add gas boundaries
1 parent 3e75393 commit dfbcc10

File tree

3 files changed

+251
-232
lines changed

3 files changed

+251
-232
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,9 @@ def selfdestruct(evm: Evm) -> None:
625625
if is_cold_access:
626626
gas_cost += GAS_COLD_ACCOUNT_ACCESS
627627

628+
check_gas(evm, gas_cost)
629+
630+
# is_account_alive requires account to be accessed, check gas before
628631
if (
629632
not is_account_alive(evm.message.block_env.state, beneficiary)
630633
and get_account(
@@ -634,11 +637,8 @@ def selfdestruct(evm: Evm) -> None:
634637
):
635638
gas_cost += GAS_SELF_DESTRUCT_NEW_ACCOUNT
636639

637-
check_gas(evm, gas_cost)
638-
639640
if is_cold_access:
640641
evm.accessed_addresses.add(beneficiary)
641-
642642
track_address(evm.state_changes, beneficiary)
643643

644644
charge_gas(evm, gas_cost)

tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py

Lines changed: 1 addition & 228 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Tests for EIP-7928 using the consistent data class pattern."""
22

3-
from typing import Callable, Dict
3+
from typing import Callable
44

55
import pytest
66
from execution_testing import (
@@ -21,7 +21,6 @@
2121
Fork,
2222
Hash,
2323
Header,
24-
Initcode,
2524
Op,
2625
Transaction,
2726
compute_create_address,
@@ -217,232 +216,6 @@ def test_bal_code_changes(
217216
)
218217

219218

220-
@pytest.mark.parametrize(
221-
"self_destruct_in_same_tx", [True, False], ids=["same_tx", "new_tx"]
222-
)
223-
@pytest.mark.parametrize(
224-
"pre_funded", [True, False], ids=["pre_funded", "not_pre_funded"]
225-
)
226-
def test_bal_self_destruct(
227-
pre: Alloc,
228-
blockchain_test: BlockchainTestFiller,
229-
self_destruct_in_same_tx: bool,
230-
pre_funded: bool,
231-
) -> None:
232-
"""Ensure BAL captures balance changes caused by `SELFDESTRUCT`."""
233-
alice = pre.fund_eoa()
234-
bob = pre.fund_eoa(amount=0)
235-
236-
selfdestruct_code = (
237-
Op.SLOAD(0x01) # Read from storage slot 0x01
238-
+ Op.SSTORE(0x02, 0x42) # Write to storage slot 0x02
239-
+ Op.SELFDESTRUCT(bob)
240-
)
241-
# A pre existing self-destruct contract with initial storage
242-
kaboom = pre.deploy_contract(code=selfdestruct_code, storage={0x01: 0x123})
243-
244-
# A template for self-destruct contract
245-
self_destruct_init_code = Initcode(deploy_code=selfdestruct_code)
246-
template = pre.deploy_contract(code=self_destruct_init_code)
247-
248-
transfer_amount = expected_recipient_balance = 100
249-
pre_fund_amount = 10
250-
251-
if self_destruct_in_same_tx:
252-
# The goal is to create a self-destructing contract in the same
253-
# transaction to trigger deletion of code as per EIP-6780.
254-
# The factory contract below creates a new self-destructing
255-
# contract and calls it in this transaction.
256-
257-
bytecode_size = len(self_destruct_init_code)
258-
factory_bytecode = (
259-
# Clone template memory
260-
Op.EXTCODECOPY(template, 0, 0, bytecode_size)
261-
# Fund 100 wei and deploy the clone
262-
+ Op.CREATE(transfer_amount, 0, bytecode_size)
263-
# Call the clone, which self-destructs
264-
+ Op.CALL(100_000, Op.DUP6, 0, 0, 0, 0, 0)
265-
+ Op.STOP
266-
)
267-
268-
factory = pre.deploy_contract(code=factory_bytecode)
269-
kaboom_same_tx = compute_create_address(address=factory, nonce=1)
270-
271-
# Determine which account will be self-destructed
272-
self_destructed_account = (
273-
kaboom_same_tx if self_destruct_in_same_tx else kaboom
274-
)
275-
276-
if pre_funded:
277-
expected_recipient_balance += pre_fund_amount
278-
pre.fund_address(
279-
address=self_destructed_account, amount=pre_fund_amount
280-
)
281-
282-
tx = Transaction(
283-
sender=alice,
284-
to=factory if self_destruct_in_same_tx else kaboom,
285-
value=transfer_amount,
286-
gas_limit=1_000_000,
287-
gas_price=0xA,
288-
)
289-
290-
block = Block(
291-
txs=[tx],
292-
expected_block_access_list=BlockAccessListExpectation(
293-
account_expectations={
294-
alice: BalAccountExpectation(
295-
nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)],
296-
),
297-
bob: BalAccountExpectation(
298-
balance_changes=[
299-
BalBalanceChange(
300-
tx_index=1, post_balance=expected_recipient_balance
301-
)
302-
]
303-
),
304-
self_destructed_account: BalAccountExpectation(
305-
balance_changes=[
306-
BalBalanceChange(tx_index=1, post_balance=0)
307-
]
308-
if pre_funded
309-
else [],
310-
# Accessed slots for same-tx are recorded as reads (0x02)
311-
storage_reads=[0x01, 0x02]
312-
if self_destruct_in_same_tx
313-
else [0x01],
314-
# Storage changes are recorded for non-same-tx
315-
# self-destructs
316-
storage_changes=[
317-
BalStorageSlot(
318-
slot=0x02,
319-
slot_changes=[
320-
BalStorageChange(tx_index=1, post_value=0x42)
321-
],
322-
)
323-
]
324-
if not self_destruct_in_same_tx
325-
else [],
326-
code_changes=[], # should not be present
327-
nonce_changes=[], # should not be present
328-
),
329-
}
330-
),
331-
)
332-
333-
post: Dict[Address, Account] = {
334-
alice: Account(nonce=1),
335-
bob: Account(balance=expected_recipient_balance),
336-
}
337-
338-
# If the account was self-destructed in the same transaction,
339-
# we expect the account to non-existent and its balance to be 0.
340-
if self_destruct_in_same_tx:
341-
post.update(
342-
{
343-
factory: Account(
344-
nonce=2, # incremented after CREATE
345-
balance=0, # spent on CREATE
346-
code=factory_bytecode,
347-
),
348-
kaboom_same_tx: Account.NONEXISTENT, # type: ignore
349-
# The pre-existing contract remains unaffected
350-
kaboom: Account(
351-
balance=0, code=selfdestruct_code, storage={0x01: 0x123}
352-
),
353-
}
354-
)
355-
else:
356-
post.update(
357-
{
358-
# This contract was self-destructed in a separate tx.
359-
# From EIP 6780: `SELFDESTRUCT` does not delete any data
360-
# (including storage keys, code, or the account itself).
361-
kaboom: Account(
362-
balance=0,
363-
code=selfdestruct_code,
364-
storage={0x01: 0x123, 0x2: 0x42},
365-
),
366-
}
367-
)
368-
369-
blockchain_test(
370-
pre=pre,
371-
blocks=[block],
372-
post=post,
373-
)
374-
375-
376-
def test_bal_self_destruct_oog(
377-
pre: Alloc,
378-
blockchain_test: BlockchainTestFiller,
379-
) -> None:
380-
"""
381-
Test that SELFDESTRUCT beneficiary is NOT included in BAL when OOG.
382-
383-
When SELFDESTRUCT runs out of gas, the operation fails and the beneficiary
384-
address should NOT be added to the Block Access List.
385-
386-
This test:
387-
1. Deploys a contract with SELFDESTRUCT bytecode
388-
2. Calls the contract with limited gas so SELFDESTRUCT fails OOG
389-
3. Verifies beneficiary is NOT in BAL (the CALL reverts, undoing BAL changes)
390-
391-
SELFDESTRUCT gas cost to cold new account: 5000 + 2600 + 25000 = 32600 gas
392-
"""
393-
alice = pre.fund_eoa()
394-
395-
# Beneficiary address for SELFDESTRUCT
396-
beneficiary = Address(0xBEEF)
397-
398-
# Contract: PUSH20 <beneficiary> SELFDESTRUCT
399-
selfdestruct_code = Op.SELFDESTRUCT(beneficiary)
400-
selfdestruct_contract = pre.deploy_contract(code=selfdestruct_code, balance=1000)
401-
402-
# Caller contract: CALL with limited gas to cause OOG on SELFDESTRUCT
403-
# SELFDESTRUCT needs 32600 gas, we give it only 100
404-
caller_code = (
405-
Op.CALL(gas=100, address=selfdestruct_contract, value=0,
406-
args_offset=0, args_size=0, ret_offset=0, ret_size=0)
407-
+ Op.STOP
408-
)
409-
caller_contract = pre.deploy_contract(code=caller_code)
410-
411-
tx = Transaction(
412-
sender=alice,
413-
to=caller_contract,
414-
gas_limit=100_000,
415-
gas_price=0xA,
416-
)
417-
418-
# The inner CALL fails OOG, so SELFDESTRUCT doesn't complete.
419-
# Beneficiary should NOT be in BAL.
420-
block = Block(
421-
txs=[tx],
422-
expected_block_access_list=BlockAccessListExpectation(
423-
account_expectations={
424-
alice: BalAccountExpectation(
425-
nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)],
426-
),
427-
caller_contract: BalAccountExpectation.empty(),
428-
selfdestruct_contract: BalAccountExpectation.empty(),
429-
# beneficiary should NOT appear - SELFDESTRUCT failed OOG
430-
}
431-
),
432-
)
433-
434-
blockchain_test(
435-
pre=pre,
436-
blocks=[block],
437-
post={
438-
alice: Account(nonce=1),
439-
caller_contract: Account(code=caller_code),
440-
# Contract still exists - SELFDESTRUCT failed
441-
selfdestruct_contract: Account(balance=1000, code=selfdestruct_code),
442-
},
443-
)
444-
445-
446219
@pytest.mark.parametrize(
447220
"account_access_opcode",
448221
[

0 commit comments

Comments
 (0)