Skip to content
Merged
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
11 changes: 11 additions & 0 deletions tests/cancun/eip6780_selfdestruct/test_journal_revert.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
Account,
Alloc,
Environment,
Fork,
Op,
StateTestFiller,
Storage,
Transaction,
TransactionReceipt,
)

REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6780.md"
Expand All @@ -22,6 +24,7 @@ def test_selfdestruct_balance_transfer_reverted(
state_test: StateTestFiller,
env: Environment,
pre: Alloc,
fork: Fork,
) -> None:
"""
Test that SELFDESTRUCT balance transfer is reverted on sub-call revert.
Expand Down Expand Up @@ -64,6 +67,13 @@ def test_selfdestruct_balance_transfer_reverted(

sender = pre.fund_eoa()

# Under EIP-7708 the SELFDESTRUCT-triggered Transfer log is emitted inside
# the reverted sub-call, so it must be discarded together with the rest of
# the reverted state.
expected_receipt = (
TransactionReceipt(logs=[]) if fork.is_eip_enabled(7708) else None
)

state_test(
env=env,
pre=pre,
Expand All @@ -78,5 +88,6 @@ def test_selfdestruct_balance_transfer_reverted(
sender=sender,
to=outer,
gas_limit=1_000_000,
expected_receipt=expected_receipt,
),
)
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
"""Suicide scenario requested test https://github.com/ethereum/tests/issues/1325."""
"""
Self-destruct scenario requested test
https://github.com/ethereum/tests/issues/1325.
"""

from typing import SupportsBytes

Expand All @@ -14,9 +17,12 @@
Op,
StateTestFiller,
Transaction,
TransactionReceipt,
)
from execution_testing.forks import Cancun

from tests.amsterdam.eip7708_eth_transfer_logs.spec import transfer_log

REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6780.md"
REFERENCE_SPEC_VERSION = "1b6a0e94cc47e859b9866e570391cf37dc55059a"

Expand Down Expand Up @@ -49,7 +55,7 @@ def selfdestruct_contract_address(

@pytest.fixture
def executor_contract_bytecode(
first_suicide: Op,
first_selfdestruct: Op,
revert_contract_address: Address,
selfdestruct_contract_address: Address,
) -> Bytecode:
Expand All @@ -58,9 +64,11 @@ def executor_contract_bytecode(
Op.SSTORE(
1,
(
first_suicide(address=selfdestruct_contract_address, value=0)
if first_suicide in [Op.CALL, Op.CALLCODE]
else first_suicide(address=selfdestruct_contract_address)
first_selfdestruct(
address=selfdestruct_contract_address, value=0
)
if first_selfdestruct in [Op.CALL, Op.CALLCODE]
else first_selfdestruct(address=selfdestruct_contract_address)
),
)
+ Op.SSTORE(2, Op.CALL(address=revert_contract_address))
Expand Down Expand Up @@ -100,14 +108,14 @@ def executor_contract_address(

@pytest.fixture
def revert_contract_bytecode(
second_suicide: Op,
second_selfdestruct: Op,
selfdestruct_contract_address: Address,
) -> Bytecode:
"""Contract code that performs a call and then reverts."""
call_op = (
second_suicide(address=selfdestruct_contract_address, value=100)
if second_suicide in [Op.CALL, Op.CALLCODE]
else second_suicide(address=selfdestruct_contract_address)
second_selfdestruct(address=selfdestruct_contract_address, value=100)
if second_selfdestruct in [Op.CALL, Op.CALLCODE]
else second_selfdestruct(address=selfdestruct_contract_address)
)
return Op.MSTORE(0, Op.ADD(15, call_op)) + Op.REVERT(0, 32)

Expand All @@ -131,18 +139,18 @@ def revert_contract_address(

@pytest.mark.valid_from("Paris")
@pytest.mark.parametrize(
"first_suicide", [Op.CALL, Op.CALLCODE, Op.DELEGATECALL]
"first_selfdestruct", [Op.CALL, Op.CALLCODE, Op.DELEGATECALL]
)
@pytest.mark.parametrize(
"second_suicide", [Op.CALL, Op.CALLCODE, Op.DELEGATECALL]
"second_selfdestruct", [Op.CALL, Op.CALLCODE, Op.DELEGATECALL]
)
def test_reentrancy_selfdestruct_revert(
pre: Alloc,
env: Environment,
sender: EOA,
fork: Fork,
first_suicide: Op,
second_suicide: Op,
first_selfdestruct: Op,
second_selfdestruct: Op,
state_test: StateTestFiller,
selfdestruct_contract_bytecode: Bytecode,
selfdestruct_contract_address: Address,
Expand Down Expand Up @@ -171,23 +179,24 @@ def test_reentrancy_selfdestruct_revert(
),
}

if first_suicide in [Op.CALLCODE, Op.DELEGATECALL]:
if first_selfdestruct in [Op.CALLCODE, Op.DELEGATECALL]:
if fork >= Cancun:
# On Cancun even callcode/delegatecall does not remove the account,
# so the value remain
post[executor_contract_address] = Account(
storage={
0x01: 0x01, # First call to contract S->suicide success
0x02: 0x00, # Second call to contract S->suicide reverted
0x01: 0x01, # 1st call to contract S->selfdestruct success
0x02: 0x00, # 2nd call to contract S->selfdestruct revert
0x03: 16, # Reverted value to check that revert really
# worked
},
)
else:
# Callcode executed first suicide from sender. sender is deleted
# Callcode executed first selfdestruct from sender.
# Sender is deleted.
post[executor_contract_address] = Account.NONEXISTENT # type: ignore

# Original suicide account remains in state
# Original selfdestruct account remains in state
post[selfdestruct_contract_address] = Account(
balance=selfdestruct_contract_init_balance, storage={}
)
Expand All @@ -196,19 +205,19 @@ def test_reentrancy_selfdestruct_revert(
balance=executor_contract_init_balance,
)

# On Cancun suicide no longer destroys the account from state, just cleans
# the balance
if first_suicide in [Op.CALL]:
# On Cancun selfdestruct no longer destroys the account from state, just
# cleans the balance
if first_selfdestruct in [Op.CALL]:
post[executor_contract_address] = Account(
storage={
0x01: 0x01, # First call to contract S->suicide success
0x02: 0x00, # Second call to contract S->suicide reverted
0x01: 0x01, # First call to contract S->selfdestruct success
0x02: 0x00, # Second call to contract S->selfdestruct reverted
0x03: 16, # Reverted value to check that revert really worked
},
)
if fork >= Cancun:
# On Cancun suicide does not remove the account, just sends the
# balance
# On Cancun selfdestruct does not remove the account, just sends
# the balance
post[selfdestruct_contract_address] = Account(
balance=0, code=selfdestruct_contract_bytecode, storage={}
)
Expand All @@ -220,11 +229,41 @@ def test_reentrancy_selfdestruct_revert(
balance=selfdestruct_contract_init_balance,
)

# Under EIP-7708 the first SELFDESTRUCT emits a Transfer log to the
# recipient; the second SELFDESTRUCT happens inside the reverted frame so
# its logs are discarded. For CALL the transfer is from S; for
# CALLCODE/DELEGATECALL the code runs in executor's context, so the
# transfer is from executor.
expected_receipt = None
if fork.is_eip_enabled(7708):
if first_selfdestruct == Op.CALL:
expected_logs = [
transfer_log(
selfdestruct_contract_address,
selfdestruct_recipient_address,
selfdestruct_contract_init_balance,
)
]
elif first_selfdestruct in [Op.CALLCODE, Op.DELEGATECALL]:
expected_logs = [
transfer_log(
executor_contract_address,
selfdestruct_recipient_address,
executor_contract_init_balance,
)
]
else:
raise RuntimeError(
f"Unexpected opcode for test: {first_selfdestruct}"
)
expected_receipt = TransactionReceipt(logs=expected_logs)

tx = Transaction(
sender=sender,
to=executor_contract_address,
gas_limit=500_000,
value=0,
expected_receipt=expected_receipt,
)

state_test(env=env, pre=pre, post=post, tx=tx)
Loading
Loading