@pytest.mark.parametrize("create_opcode", [Op.CREATE, Op.CREATE2])
@pytest.mark.parametrize(
"initial_balance",
[pytest.param(0, id="zero_balance"), pytest.param(3, id="funded")],
)
@pytest.mark.parametrize(
"steps",
[
pytest.param((SD_SELF,), id="case01_self"),
pytest.param((SD_SELF, SD_SELF), id="case02_self_self"),
pytest.param((SD_SELF, SD_OTHER), id="case03_self_other"),
pytest.param((SD_OTHER, SD_SELF), id="case04_other_self"),
pytest.param((SD_OTHER, SEND), id="case05_other_send"),
pytest.param((SD_OTHER, SEND, SEND), id="case06_other_send_send"),
pytest.param((SD_OTHER, SEND, SD_OTHER), id="case07_other_send_other"),
pytest.param((SD_OTHER, SEND, SD_SELF), id="case08_other_send_self"),
pytest.param((SD_SELF, SEND), id="case09_self_send"),
pytest.param((SD_SELF, SEND, SEND), id="case10_self_send_send"),
pytest.param((SD_SELF, SEND, SD_OTHER), id="case11_self_send_other"),
pytest.param((SD_SELF, SEND, SD_SELF), id="case12_self_send_self"),
],
)
@EIPChecklist.Opcode.Test.ExecutionContext.Call(eip=[8246])
@EIPChecklist.Opcode.Test.GasUsage.ExtraGas(eip=[8246])
@EIPChecklist.Opcode.Test.Terminating.Scenarios.SubLevel(eip=[8246])
def test_selfdestruct_sequences(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
create_opcode: Op,
steps: tuple[str, ...],
initial_balance: int,
) -> None:
"""
Cases 1-12 of the EIP-8246 test list: a contract created in this
transaction runs a sequence of self-destructs and value sends, and
whatever it still holds at the end is what it keeps.
"""
sender = pre.fund_eoa()
other = pre.fund_eoa(amount=OTHER_BALANCE)
# The victim self-destructs to the address in calldata, or just takes
# the value when calldata is zero.
victim_code = (
Conditional(
condition=Op.CALLDATALOAD(0),
if_true=Op.SELFDESTRUCT(Op.CALLDATALOAD(0)),
)
+ Op.STOP
)
initcode = Initcode(deploy_code=victim_code)
if create_opcode == Op.CREATE:
create = Op.CREATE(value=initial_balance, offset=0, size=len(initcode))
elif create_opcode == Op.CREATE2:
create = Op.CREATE2(
value=initial_balance, offset=0, size=len(initcode), salt=0
)
else:
raise ValueError(f"unhandled create opcode {create_opcode}")
entry_code = Om.MSTORE(initcode, 0) + Op.SSTORE(0, create)
for slot, step in enumerate(steps, 1):
if step == SD_SELF:
calldata: Address | Bytecode | int = Op.SLOAD(0)
value = 0
elif step == SD_OTHER:
calldata = other
value = 0
elif step == SEND:
calldata = 0
value = SEND_AMOUNT
else:
raise ValueError(f"unhandled step {step}")
entry_code += Op.MSTORE(0, calldata) + Op.SSTORE(
slot,
Op.CALL(
gas=Op.GAS,
address=Op.SLOAD(0),
value=value,
args_offset=0,
args_size=32,
),
)
entry = pre.deploy_contract(code=entry_code + Op.STOP)
victim = compute_create_address(
address=entry,
nonce=1,
salt=0,
initcode=initcode,
opcode=create_opcode,
)
tx_value = initial_balance + SEND_AMOUNT * steps.count(SEND)
tx = Transaction(sender=sender, to=entry, value=tx_value)
victim_balance = initial_balance
other_balance = OTHER_BALANCE
logs: list[TransactionLog] = []
if tx_value > 0:
logs.append(transfer_log(sender, entry, tx_value))
if initial_balance > 0:
logs.append(transfer_log(entry, victim, initial_balance))
for step in steps:
if step == SD_SELF:
# Before EIP-8246 the opcode burns a same-tx contract's balance.
if not fork.is_eip_enabled(8246):
victim_balance = 0
elif step == SD_OTHER:
if victim_balance > 0:
logs.append(transfer_log(victim, other, victim_balance))
other_balance += victim_balance
victim_balance = 0
elif step == SEND:
logs.append(transfer_log(entry, victim, SEND_AMOUNT))
victim_balance += SEND_AMOUNT
else:
raise ValueError(f"unhandled step {step}")
if fork.is_eip_enabled(7708):
tx.expected_receipt = TransactionReceipt(logs=logs)
expected_bal = None
if fork.is_eip_enabled(7928):
expected_bal = BlockAccessListExpectation(
account_expectations={
victim: finalized_bal(fork, victim_balance, [])
}
)
entry_storage: dict[int, Address | int] = {0: victim}
for slot in range(1, len(steps) + 1):
entry_storage[slot] = 1
state_test(
pre=pre,
post={
entry: Account(balance=0, storage=entry_storage),
victim: finalized(fork, victim_balance),
other: Account(balance=other_balance),
},
tx=tx,
expected_block_access_list=expected_bal,
)