Skip to content

test_reentrancy_selfdestruct_revert()

Documentation for tests/cancun/eip6780_selfdestruct/test_reentrancy_selfdestruct_revert.py::test_reentrancy_selfdestruct_revert@b47f0253.

Generate fixtures for these test cases for Amsterdam with:

fill -v tests/cancun/eip6780_selfdestruct/test_reentrancy_selfdestruct_revert.py::test_reentrancy_selfdestruct_revert --fork Amsterdam

Suicide reentrancy scenario.

Call|Callcode|Delegatecall the contract S. S self destructs. Call the revert proxy contract R. R Calls|Callcode|Delegatecall S. S self destructs (for the second time). R reverts (including the effects of the second selfdestruct). It is expected the S is self destructed after the transaction.

Source code in tests/cancun/eip6780_selfdestruct/test_reentrancy_selfdestruct_revert.py
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
@pytest.mark.valid_from("Paris")
@pytest.mark.parametrize(
    "first_selfdestruct", [Op.CALL, Op.CALLCODE, Op.DELEGATECALL]
)
@pytest.mark.parametrize(
    "second_selfdestruct", [Op.CALL, Op.CALLCODE, Op.DELEGATECALL]
)
def test_reentrancy_selfdestruct_revert(
    pre: Alloc,
    env: Environment,
    sender: EOA,
    fork: Fork,
    first_selfdestruct: Op,
    second_selfdestruct: Op,
    state_test: StateTestFiller,
    selfdestruct_contract_bytecode: Bytecode,
    selfdestruct_contract_address: Address,
    selfdestruct_contract_init_balance: int,
    revert_contract_address: Address,
    revert_contract_init_balance: int,
    executor_contract_address: Address,
    executor_contract_init_balance: int,
    selfdestruct_recipient_address: Address,
) -> None:
    """
    Suicide reentrancy scenario.

    Call|Callcode|Delegatecall the contract S.
    S self destructs.
    Call the revert proxy contract R.
    R Calls|Callcode|Delegatecall S.
    S self destructs (for the second time).
    R reverts (including the effects of the second selfdestruct).
    It is expected the S is self destructed after the transaction.
    """
    post = {
        # Second caller unchanged as call gets reverted
        revert_contract_address: Account(
            balance=revert_contract_init_balance, storage={}
        ),
    }

    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,  # 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 selfdestruct from sender.
            # Sender is deleted.
            post[executor_contract_address] = Account.NONEXISTENT  # type: ignore

        # Original selfdestruct account remains in state
        post[selfdestruct_contract_address] = Account(
            balance=selfdestruct_contract_init_balance, storage={}
        )
        # Suicide destination
        post[selfdestruct_recipient_address] = Account(
            balance=executor_contract_init_balance,
        )

    # 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->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 selfdestruct does not remove the account, just sends
            # the balance
            post[selfdestruct_contract_address] = Account(
                balance=0, code=selfdestruct_contract_bytecode, storage={}
            )
        else:
            post[selfdestruct_contract_address] = Account.NONEXISTENT  # type: ignore

        # Suicide destination
        post[selfdestruct_recipient_address] = Account(
            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)

Parametrized Test Cases

This test generates 9 parametrized test cases across 6 forks.