|
1 | 1 | """Tests for EIP-7928 using the consistent data class pattern.""" |
2 | 2 |
|
| 3 | +from typing import Dict |
| 4 | + |
3 | 5 | import pytest |
4 | 6 |
|
| 7 | +from ethereum_test_base_types import Address |
5 | 8 | from ethereum_test_tools import ( |
6 | 9 | Account, |
7 | 10 | Alloc, |
8 | 11 | Block, |
9 | 12 | BlockchainTestFiller, |
| 13 | + Initcode, |
10 | 14 | Storage, |
11 | 15 | Transaction, |
12 | 16 | compute_create_address, |
@@ -284,3 +288,139 @@ def test_bal_code_changes( |
284 | 288 | ), |
285 | 289 | }, |
286 | 290 | ) |
| 291 | + |
| 292 | + |
| 293 | +@pytest.mark.valid_from("Amsterdam") |
| 294 | +@pytest.mark.parametrize("self_destruct_in_same_tx", [True, False], ids=["same_tx", "new_tx"]) |
| 295 | +@pytest.mark.parametrize("pre_funded", [True, False], ids=["pre_funded", "not_pre_funded"]) |
| 296 | +def test_bal_self_destruct( |
| 297 | + pre: Alloc, |
| 298 | + blockchain_test: BlockchainTestFiller, |
| 299 | + self_destruct_in_same_tx: bool, |
| 300 | + pre_funded: bool, |
| 301 | +): |
| 302 | + """Ensure BAL captures balance changes caused by `SELFDESTRUCT`.""" |
| 303 | + alice = pre.fund_eoa() |
| 304 | + bob = pre.fund_eoa(amount=0) |
| 305 | + |
| 306 | + selfdestruct_code = ( |
| 307 | + Op.SLOAD(0x01) # Read from storage slot 0x01 |
| 308 | + + Op.SSTORE(0x02, 0x42) # Write to storage slot 0x02 |
| 309 | + + Op.SELFDESTRUCT(bob) |
| 310 | + ) |
| 311 | + # A pre existing self-destruct contract with initial storage |
| 312 | + kaboom = pre.deploy_contract(code=selfdestruct_code, storage={0x01: 0x123}) |
| 313 | + |
| 314 | + # A template for self-destruct contract |
| 315 | + self_destruct_init_code = Initcode(deploy_code=selfdestruct_code) |
| 316 | + template = pre.deploy_contract(code=self_destruct_init_code) |
| 317 | + |
| 318 | + transfer_amount = expected_recipient_balance = 100 |
| 319 | + pre_fund_amount = 10 |
| 320 | + |
| 321 | + if self_destruct_in_same_tx: |
| 322 | + # The goal is to create a self-destructing contract in the same |
| 323 | + # transaction to trigger deletion of code as per EIP-6780. |
| 324 | + # The factory contract below creates a new self-destructing |
| 325 | + # contract and calls it in this transaction. |
| 326 | + |
| 327 | + bytecode_size = len(self_destruct_init_code) |
| 328 | + factory_bytecode = ( |
| 329 | + # Clone template memory |
| 330 | + Op.EXTCODECOPY(template, 0, 0, bytecode_size) |
| 331 | + # Fund 100 wei and deploy the clone |
| 332 | + + Op.CREATE(transfer_amount, 0, bytecode_size) |
| 333 | + # Call the clone, which self-destructs |
| 334 | + + Op.CALL(100_000, Op.DUP6, 0, 0, 0, 0, 0) |
| 335 | + + Op.STOP |
| 336 | + ) |
| 337 | + |
| 338 | + factory = pre.deploy_contract(code=factory_bytecode) |
| 339 | + kaboom_same_tx = compute_create_address(address=factory, nonce=1) |
| 340 | + |
| 341 | + # Determine which account will be self-destructed |
| 342 | + self_destructed_account = kaboom_same_tx if self_destruct_in_same_tx else kaboom |
| 343 | + |
| 344 | + if pre_funded: |
| 345 | + expected_recipient_balance += pre_fund_amount |
| 346 | + pre.fund_address(address=self_destructed_account, amount=pre_fund_amount) |
| 347 | + |
| 348 | + tx = Transaction( |
| 349 | + sender=alice, |
| 350 | + to=factory if self_destruct_in_same_tx else kaboom, |
| 351 | + value=transfer_amount, |
| 352 | + gas_limit=1_000_000, |
| 353 | + gas_price=0xA, |
| 354 | + ) |
| 355 | + |
| 356 | + block = Block( |
| 357 | + txs=[tx], |
| 358 | + expected_block_access_list=BlockAccessListExpectation( |
| 359 | + account_expectations={ |
| 360 | + alice: BalAccountExpectation( |
| 361 | + nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)], |
| 362 | + ), |
| 363 | + bob: BalAccountExpectation( |
| 364 | + balance_changes=[ |
| 365 | + BalBalanceChange(tx_index=1, post_balance=expected_recipient_balance) |
| 366 | + ] |
| 367 | + ), |
| 368 | + self_destructed_account: BalAccountExpectation( |
| 369 | + balance_changes=[BalBalanceChange(tx_index=1, post_balance=0)] |
| 370 | + if pre_funded |
| 371 | + else [], |
| 372 | + # Accessed slots for same-tx are recorded as reads (0x02) |
| 373 | + storage_reads=[0x01, 0x02] if self_destruct_in_same_tx else [0x01], |
| 374 | + # Storage changes are recorded for non-same-tx |
| 375 | + # self-destructs |
| 376 | + storage_changes=[ |
| 377 | + BalStorageSlot( |
| 378 | + slot=0x02, slot_changes=[BalStorageChange(tx_index=1, post_value=0x42)] |
| 379 | + ) |
| 380 | + ] |
| 381 | + if not self_destruct_in_same_tx |
| 382 | + else [], |
| 383 | + code_changes=[], # should not be present |
| 384 | + nonce_changes=[], # should not be present |
| 385 | + ), |
| 386 | + } |
| 387 | + ), |
| 388 | + ) |
| 389 | + |
| 390 | + post: Dict[Address, Account] = { |
| 391 | + alice: Account(nonce=1), |
| 392 | + bob: Account(balance=expected_recipient_balance), |
| 393 | + } |
| 394 | + |
| 395 | + # If the account was self-destructed in the same transaction, |
| 396 | + # we expect the account to non-existent and its balance to be 0. |
| 397 | + if self_destruct_in_same_tx: |
| 398 | + post.update( |
| 399 | + { |
| 400 | + factory: Account( |
| 401 | + nonce=2, # incremented after CREATE |
| 402 | + balance=0, # spent on CREATE |
| 403 | + code=factory_bytecode, |
| 404 | + ), |
| 405 | + kaboom_same_tx: Account.NONEXISTENT, # type: ignore |
| 406 | + # The pre-existing contract remains unaffected |
| 407 | + kaboom: Account(balance=0, code=selfdestruct_code, storage={0x01: 0x123}), |
| 408 | + } |
| 409 | + ) |
| 410 | + else: |
| 411 | + post.update( |
| 412 | + { |
| 413 | + # This contract was self-destructed in a separate tx. |
| 414 | + # From EIP 6780: `SELFDESTRUCT` does not delete any data |
| 415 | + # (including storage keys, code, or the account itself). |
| 416 | + kaboom: Account( |
| 417 | + balance=0, code=selfdestruct_code, storage={0x01: 0x123, 0x2: 0x42} |
| 418 | + ), |
| 419 | + } |
| 420 | + ) |
| 421 | + |
| 422 | + blockchain_test( |
| 423 | + pre=pre, |
| 424 | + blocks=[block], |
| 425 | + post=post, |
| 426 | + ) |
0 commit comments