|
| 1 | +""" |
| 2 | +Suicide scenario requested test |
| 3 | +https://github.com/ethereum/tests/issues/1325 |
| 4 | +""" |
| 5 | + |
| 6 | +import pytest |
| 7 | + |
| 8 | +from ethereum_test_forks import Cancun, Fork |
| 9 | +from ethereum_test_tools import ( |
| 10 | + Account, |
| 11 | + Environment, |
| 12 | + StateTestFiller, |
| 13 | + TestAddress, |
| 14 | + TestAddress2, |
| 15 | + Transaction, |
| 16 | + to_address, |
| 17 | +) |
| 18 | +from ethereum_test_tools.vm.opcode import Opcodes as Op |
| 19 | + |
| 20 | +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6780.md" |
| 21 | +REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" |
| 22 | + |
| 23 | + |
| 24 | +@pytest.fixture |
| 25 | +def env(): # noqa: D103 |
| 26 | + return Environment( |
| 27 | + coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", |
| 28 | + difficulty=0x020000, |
| 29 | + gas_limit=71794957647893862, |
| 30 | + number=1, |
| 31 | + timestamp=1000, |
| 32 | + ) |
| 33 | + |
| 34 | + |
| 35 | +@pytest.mark.valid_from("Merge") |
| 36 | +@pytest.mark.parametrize("first_suicide", [Op.CALL, Op.CALLCODE, Op.DELEGATECALL]) |
| 37 | +@pytest.mark.parametrize("second_suicide", [Op.CALL, Op.CALLCODE, Op.DELEGATECALL]) |
| 38 | +def test_reentrancy_selfdestruct_revert( |
| 39 | + env: Environment, |
| 40 | + fork: Fork, |
| 41 | + first_suicide: Op, |
| 42 | + second_suicide: Op, |
| 43 | + state_test: StateTestFiller, |
| 44 | +): |
| 45 | + """ |
| 46 | + Suicide reentrancy scenario: |
| 47 | +
|
| 48 | + Call|Callcode|Delegatecall the contract S. |
| 49 | + S self destructs. |
| 50 | + Call the revert proxy contract R. |
| 51 | + R Calls|Callcode|Delegatecall S. |
| 52 | + S self destructs (for the second time). |
| 53 | + R reverts (including the effects of the second selfdestruct). |
| 54 | + It is expected the S is self destructed after the transaction. |
| 55 | + """ |
| 56 | + address_to = TestAddress2 |
| 57 | + address_s = to_address(0x1000000000000000000000000000000000000001) |
| 58 | + address_r = to_address(0x1000000000000000000000000000000000000002) |
| 59 | + suicide_d = to_address(0x03E8) |
| 60 | + |
| 61 | + def construct_call_s(call_type: Op, money: int): |
| 62 | + if call_type in [Op.CALLCODE, Op.CALL]: |
| 63 | + return call_type(Op.GAS, Op.PUSH20(address_s), money, 0, 0, 0, 0) |
| 64 | + else: |
| 65 | + return call_type(Op.GAS, Op.PUSH20(address_s), money, 0, 0, 0) |
| 66 | + |
| 67 | + pre = { |
| 68 | + address_to: Account( |
| 69 | + balance=1000000000000000000, |
| 70 | + nonce=0, |
| 71 | + code=Op.SSTORE(1, construct_call_s(first_suicide, 0)) |
| 72 | + + Op.SSTORE(2, Op.CALL(Op.GAS, Op.PUSH20(address_r), 0, 0, 0, 0, 0)) |
| 73 | + + Op.RETURNDATACOPY(0, 0, Op.RETURNDATASIZE()) |
| 74 | + + Op.SSTORE(3, Op.MLOAD(0)), |
| 75 | + storage={0x01: 0x0100, 0x02: 0x0100, 0x03: 0x0100}, |
| 76 | + ), |
| 77 | + address_s: Account( |
| 78 | + balance=3000000000000000000, |
| 79 | + nonce=0, |
| 80 | + code=Op.SELFDESTRUCT(1000), |
| 81 | + storage={}, |
| 82 | + ), |
| 83 | + address_r: Account( |
| 84 | + balance=5000000000000000000, |
| 85 | + nonce=0, |
| 86 | + # Send money when calling it suicide second time to make sure the funds not transferred |
| 87 | + code=Op.MSTORE(0, Op.ADD(15, construct_call_s(second_suicide, 100))) |
| 88 | + + Op.REVERT(0, 32), |
| 89 | + storage={}, |
| 90 | + ), |
| 91 | + TestAddress: Account( |
| 92 | + balance=7000000000000000000, |
| 93 | + nonce=0, |
| 94 | + code="0x", |
| 95 | + storage={}, |
| 96 | + ), |
| 97 | + } |
| 98 | + |
| 99 | + post = { |
| 100 | + # Second caller unchanged as call gets reverted |
| 101 | + address_r: Account(balance=5000000000000000000, storage={}), |
| 102 | + } |
| 103 | + |
| 104 | + if first_suicide in [Op.CALLCODE, Op.DELEGATECALL]: |
| 105 | + if fork >= Cancun: |
| 106 | + # On Cancun even callcode/delegatecall does not remove the account, so the value remain |
| 107 | + post[address_to] = Account( |
| 108 | + storage={ |
| 109 | + 0x01: 0x01, # First call to contract S->suicide success |
| 110 | + 0x02: 0x00, # Second call to contract S->suicide reverted |
| 111 | + 0x03: 16, # Reverted value to check that revert really worked |
| 112 | + }, |
| 113 | + ) |
| 114 | + else: |
| 115 | + # Callcode executed first suicide from sender. sender is deleted |
| 116 | + post[address_to] = Account.NONEXISTENT # type: ignore |
| 117 | + |
| 118 | + # Original suicide account remains in state |
| 119 | + post[address_s] = Account(balance=3000000000000000000, storage={}) |
| 120 | + # Suicide destination |
| 121 | + post[suicide_d] = Account( |
| 122 | + balance=1000000000000000000, |
| 123 | + ) |
| 124 | + |
| 125 | + # On Cancun suicide no longer destroys the account from state, just cleans the balance |
| 126 | + if first_suicide in [Op.CALL]: |
| 127 | + post[address_to] = Account( |
| 128 | + storage={ |
| 129 | + 0x01: 0x01, # First call to contract S->suicide success |
| 130 | + 0x02: 0x00, # Second call to contract S->suicide reverted |
| 131 | + 0x03: 16, # Reverted value to check that revert really worked |
| 132 | + }, |
| 133 | + ) |
| 134 | + if fork >= Cancun: |
| 135 | + # On Cancun suicide does not remove the account, just sends the balance |
| 136 | + post[address_s] = Account(balance=0, code="0x6103e8ff", storage={}) |
| 137 | + else: |
| 138 | + post[address_s] = Account.NONEXISTENT # type: ignore |
| 139 | + |
| 140 | + # Suicide destination |
| 141 | + post[suicide_d] = Account( |
| 142 | + balance=3000000000000000000, |
| 143 | + ) |
| 144 | + |
| 145 | + tx = Transaction( |
| 146 | + ty=0x0, |
| 147 | + chain_id=0x0, |
| 148 | + nonce=0, |
| 149 | + to=address_to, |
| 150 | + gas_price=10, |
| 151 | + protected=False, |
| 152 | + data="", |
| 153 | + gas_limit=500000, |
| 154 | + value=0, |
| 155 | + ) |
| 156 | + |
| 157 | + state_test(env=env, pre=pre, post=post, txs=[tx]) |
0 commit comments