1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782 | @pytest.mark.parametrize("create_opcode", [Op.CREATE, Op.CREATE2])
@pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 100_000])
@pytest.mark.parametrize(
"call_times,sendall_recipient_addresses",
[
pytest.param(1, [PRE_DEPLOY_CONTRACT_1], id="single_call"),
pytest.param(
5, [PRE_DEPLOY_CONTRACT_1], id="multiple_calls_single beneficiary"
),
],
indirect=["sendall_recipient_addresses"],
)
@pytest.mark.valid_from("Shanghai")
def test_create_selfdestruct_same_tx_increased_nonce(
state_test: StateTestFiller,
pre: Alloc,
sender: EOA,
fork: Fork,
selfdestruct_code: Bytecode,
sendall_recipient_addresses: List[Address],
create_opcode: Op,
call_times: int,
selfdestruct_contract_initial_balance: int,
) -> None:
"""
Verify that a contract can self-destruct if it was created in the same
transaction, even when its nonce has been increased due to contract
creation.
"""
initcode = Op.RETURN(0, 1)
selfdestruct_pre_bytecode = Op.MSTORE(
0, Op.PUSH32(bytes(initcode))
) + Op.POP(Op.CREATE(offset=32 - len(initcode), size=len(initcode)))
selfdestruct_code = selfdestruct_pre_bytecode + selfdestruct_code
selfdestruct_contract_initcode = Initcode(deploy_code=selfdestruct_code)
initcode_copy_from_address = pre.deploy_contract(
selfdestruct_contract_initcode
)
selfdestruct_contract_address = compute_create_address(
address=compute_create_address(address=sender, nonce=0),
nonce=1,
initcode=selfdestruct_contract_initcode,
opcode=create_opcode,
)
if selfdestruct_contract_initial_balance > 0:
pre.fund_address(
selfdestruct_contract_address,
selfdestruct_contract_initial_balance,
)
# Our entry point is an initcode that in turn creates a self-destructing
# contract
entry_code_storage = Storage()
# Create a dict to record the expected final balances
sendall_final_balances = dict(
zip(
sendall_recipient_addresses,
[0] * len(sendall_recipient_addresses),
strict=False,
)
)
selfdestruct_contract_current_balance = (
selfdestruct_contract_initial_balance
)
# Bytecode used to create the contract, can be CREATE or CREATE2
create_bytecode = create_opcode(size=len(selfdestruct_contract_initcode))
# Entry code that will be executed, creates the contract and then calls it
# in the same tx
entry_code = (
# Initcode is already deployed at `initcode_copy_from_address`, so just
# copy it
Op.EXTCODECOPY(
initcode_copy_from_address,
0,
0,
len(selfdestruct_contract_initcode),
)
# And we store the created address for verification purposes
+ Op.SSTORE(
entry_code_storage.store_next(selfdestruct_contract_address),
create_bytecode,
)
)
# Store the EXTCODE* properties of the created address
entry_code += Op.SSTORE(
entry_code_storage.store_next(len(selfdestruct_code)),
Op.EXTCODESIZE(selfdestruct_contract_address),
)
entry_code += Op.SSTORE(
entry_code_storage.store_next(selfdestruct_code.keccak256()),
Op.EXTCODEHASH(selfdestruct_contract_address),
)
entry_code_address = compute_create_address(address=sender, nonce=0)
# Call the self-destructing contract multiple times as required, increasing
# the wei sent each time
expected_logs_after_tx_value: list = []
entry_code_balance = 0
for i, sendall_recipient in zip(
range(call_times), cycle(sendall_recipient_addresses)
):
entry_code += Op.MSTORE(0, sendall_recipient)
entry_code += Op.SSTORE(
entry_code_storage.store_next(1),
Op.CALL(
Op.GASLIMIT, # Gas
selfdestruct_contract_address, # Address
i, # Value
0,
32,
0,
0,
),
)
entry_code_balance += i
selfdestruct_contract_current_balance += i
# CALL with value > 0 emits a Transfer log (entry_code -> contract).
# The inner CREATE(value=0) prepended to selfdestruct_code does not
# emit a log (zero value).
if i > 0:
expected_logs_after_tx_value.append(
transfer_log(
entry_code_address, selfdestruct_contract_address, i
)
)
# SELFDESTRUCT always sends to a pre-deployed recipient in this test
# (SELF_ADDRESS is not parametrized here), so a Transfer log is
# emitted whenever the contract has a nonzero balance.
if selfdestruct_contract_current_balance > 0:
expected_logs_after_tx_value.append(
transfer_log(
selfdestruct_contract_address,
sendall_recipient,
selfdestruct_contract_current_balance,
)
)
# Balance is always sent to other contracts
if sendall_recipient != selfdestruct_contract_address:
sendall_final_balances[sendall_recipient] += (
selfdestruct_contract_current_balance
)
# Self-destructing contract must always have zero balance after the
# call because the self-destruct always happens in the same transaction
# in this test
selfdestruct_contract_current_balance = 0
entry_code += Op.SSTORE(
entry_code_storage.store_next(0),
Op.BALANCE(selfdestruct_contract_address),
)
# Check the EXTCODE* properties of the self-destructing contract again
entry_code += Op.SSTORE(
entry_code_storage.store_next(len(selfdestruct_code)),
Op.EXTCODESIZE(selfdestruct_contract_address),
)
entry_code += Op.SSTORE(
entry_code_storage.store_next(selfdestruct_code.keccak256()),
Op.EXTCODEHASH(selfdestruct_contract_address),
)
# Lastly return zero so the entry point contract is created and we can
# retain the stored values for verification.
entry_code += Op.RETURN(max(len(selfdestruct_contract_initcode), 32), 1)
tx = Transaction(
value=entry_code_balance,
data=entry_code,
sender=sender,
to=None,
gas_limit=1_000_000,
)
assert tx.created_contract == entry_code_address
post: Dict[Address, Account] = {
entry_code_address: Account(
code="0x00",
storage=entry_code_storage,
),
initcode_copy_from_address: Account(
code=selfdestruct_contract_initcode,
),
}
# Check the balances of the sendall recipients
for address, balance in sendall_final_balances.items():
post[address] = Account(balance=balance, storage={0: 1})
# Check the new contracts created from the self-destructing contract were
# correctly created.
for address in [
compute_create_address(
address=selfdestruct_contract_address, nonce=i + 1
)
for i in range(call_times)
]:
post[address] = Account(
code=b"\x00",
)
post[selfdestruct_contract_address] = Account.NONEXISTENT # type: ignore
if fork.is_eip_enabled(7708):
expected_logs = []
if entry_code_balance > 0:
expected_logs.append(
transfer_log(sender, entry_code_address, entry_code_balance)
)
expected_logs.extend(expected_logs_after_tx_value)
tx.expected_receipt = TransactionReceipt(logs=expected_logs)
state_test(pre=pre, post=post, tx=tx)
|