|
1 | 1 | """Tests for EIP-7928 using the consistent data class pattern.""" |
2 | 2 |
|
3 | | -from typing import Callable, Dict |
| 3 | +from typing import Callable |
4 | 4 |
|
5 | 5 | import pytest |
6 | 6 | from execution_testing import ( |
|
21 | 21 | Fork, |
22 | 22 | Hash, |
23 | 23 | Header, |
24 | | - Initcode, |
25 | 24 | Op, |
26 | 25 | Transaction, |
27 | 26 | compute_create_address, |
@@ -217,232 +216,6 @@ def test_bal_code_changes( |
217 | 216 | ) |
218 | 217 |
|
219 | 218 |
|
220 | | -@pytest.mark.parametrize( |
221 | | - "self_destruct_in_same_tx", [True, False], ids=["same_tx", "new_tx"] |
222 | | -) |
223 | | -@pytest.mark.parametrize( |
224 | | - "pre_funded", [True, False], ids=["pre_funded", "not_pre_funded"] |
225 | | -) |
226 | | -def test_bal_self_destruct( |
227 | | - pre: Alloc, |
228 | | - blockchain_test: BlockchainTestFiller, |
229 | | - self_destruct_in_same_tx: bool, |
230 | | - pre_funded: bool, |
231 | | -) -> None: |
232 | | - """Ensure BAL captures balance changes caused by `SELFDESTRUCT`.""" |
233 | | - alice = pre.fund_eoa() |
234 | | - bob = pre.fund_eoa(amount=0) |
235 | | - |
236 | | - selfdestruct_code = ( |
237 | | - Op.SLOAD(0x01) # Read from storage slot 0x01 |
238 | | - + Op.SSTORE(0x02, 0x42) # Write to storage slot 0x02 |
239 | | - + Op.SELFDESTRUCT(bob) |
240 | | - ) |
241 | | - # A pre existing self-destruct contract with initial storage |
242 | | - kaboom = pre.deploy_contract(code=selfdestruct_code, storage={0x01: 0x123}) |
243 | | - |
244 | | - # A template for self-destruct contract |
245 | | - self_destruct_init_code = Initcode(deploy_code=selfdestruct_code) |
246 | | - template = pre.deploy_contract(code=self_destruct_init_code) |
247 | | - |
248 | | - transfer_amount = expected_recipient_balance = 100 |
249 | | - pre_fund_amount = 10 |
250 | | - |
251 | | - if self_destruct_in_same_tx: |
252 | | - # The goal is to create a self-destructing contract in the same |
253 | | - # transaction to trigger deletion of code as per EIP-6780. |
254 | | - # The factory contract below creates a new self-destructing |
255 | | - # contract and calls it in this transaction. |
256 | | - |
257 | | - bytecode_size = len(self_destruct_init_code) |
258 | | - factory_bytecode = ( |
259 | | - # Clone template memory |
260 | | - Op.EXTCODECOPY(template, 0, 0, bytecode_size) |
261 | | - # Fund 100 wei and deploy the clone |
262 | | - + Op.CREATE(transfer_amount, 0, bytecode_size) |
263 | | - # Call the clone, which self-destructs |
264 | | - + Op.CALL(100_000, Op.DUP6, 0, 0, 0, 0, 0) |
265 | | - + Op.STOP |
266 | | - ) |
267 | | - |
268 | | - factory = pre.deploy_contract(code=factory_bytecode) |
269 | | - kaboom_same_tx = compute_create_address(address=factory, nonce=1) |
270 | | - |
271 | | - # Determine which account will be self-destructed |
272 | | - self_destructed_account = ( |
273 | | - kaboom_same_tx if self_destruct_in_same_tx else kaboom |
274 | | - ) |
275 | | - |
276 | | - if pre_funded: |
277 | | - expected_recipient_balance += pre_fund_amount |
278 | | - pre.fund_address( |
279 | | - address=self_destructed_account, amount=pre_fund_amount |
280 | | - ) |
281 | | - |
282 | | - tx = Transaction( |
283 | | - sender=alice, |
284 | | - to=factory if self_destruct_in_same_tx else kaboom, |
285 | | - value=transfer_amount, |
286 | | - gas_limit=1_000_000, |
287 | | - gas_price=0xA, |
288 | | - ) |
289 | | - |
290 | | - block = Block( |
291 | | - txs=[tx], |
292 | | - expected_block_access_list=BlockAccessListExpectation( |
293 | | - account_expectations={ |
294 | | - alice: BalAccountExpectation( |
295 | | - nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)], |
296 | | - ), |
297 | | - bob: BalAccountExpectation( |
298 | | - balance_changes=[ |
299 | | - BalBalanceChange( |
300 | | - tx_index=1, post_balance=expected_recipient_balance |
301 | | - ) |
302 | | - ] |
303 | | - ), |
304 | | - self_destructed_account: BalAccountExpectation( |
305 | | - balance_changes=[ |
306 | | - BalBalanceChange(tx_index=1, post_balance=0) |
307 | | - ] |
308 | | - if pre_funded |
309 | | - else [], |
310 | | - # Accessed slots for same-tx are recorded as reads (0x02) |
311 | | - storage_reads=[0x01, 0x02] |
312 | | - if self_destruct_in_same_tx |
313 | | - else [0x01], |
314 | | - # Storage changes are recorded for non-same-tx |
315 | | - # self-destructs |
316 | | - storage_changes=[ |
317 | | - BalStorageSlot( |
318 | | - slot=0x02, |
319 | | - slot_changes=[ |
320 | | - BalStorageChange(tx_index=1, post_value=0x42) |
321 | | - ], |
322 | | - ) |
323 | | - ] |
324 | | - if not self_destruct_in_same_tx |
325 | | - else [], |
326 | | - code_changes=[], # should not be present |
327 | | - nonce_changes=[], # should not be present |
328 | | - ), |
329 | | - } |
330 | | - ), |
331 | | - ) |
332 | | - |
333 | | - post: Dict[Address, Account] = { |
334 | | - alice: Account(nonce=1), |
335 | | - bob: Account(balance=expected_recipient_balance), |
336 | | - } |
337 | | - |
338 | | - # If the account was self-destructed in the same transaction, |
339 | | - # we expect the account to non-existent and its balance to be 0. |
340 | | - if self_destruct_in_same_tx: |
341 | | - post.update( |
342 | | - { |
343 | | - factory: Account( |
344 | | - nonce=2, # incremented after CREATE |
345 | | - balance=0, # spent on CREATE |
346 | | - code=factory_bytecode, |
347 | | - ), |
348 | | - kaboom_same_tx: Account.NONEXISTENT, # type: ignore |
349 | | - # The pre-existing contract remains unaffected |
350 | | - kaboom: Account( |
351 | | - balance=0, code=selfdestruct_code, storage={0x01: 0x123} |
352 | | - ), |
353 | | - } |
354 | | - ) |
355 | | - else: |
356 | | - post.update( |
357 | | - { |
358 | | - # This contract was self-destructed in a separate tx. |
359 | | - # From EIP 6780: `SELFDESTRUCT` does not delete any data |
360 | | - # (including storage keys, code, or the account itself). |
361 | | - kaboom: Account( |
362 | | - balance=0, |
363 | | - code=selfdestruct_code, |
364 | | - storage={0x01: 0x123, 0x2: 0x42}, |
365 | | - ), |
366 | | - } |
367 | | - ) |
368 | | - |
369 | | - blockchain_test( |
370 | | - pre=pre, |
371 | | - blocks=[block], |
372 | | - post=post, |
373 | | - ) |
374 | | - |
375 | | - |
376 | | -def test_bal_self_destruct_oog( |
377 | | - pre: Alloc, |
378 | | - blockchain_test: BlockchainTestFiller, |
379 | | -) -> None: |
380 | | - """ |
381 | | - Test that SELFDESTRUCT beneficiary is NOT included in BAL when OOG. |
382 | | -
|
383 | | - When SELFDESTRUCT runs out of gas, the operation fails and the beneficiary |
384 | | - address should NOT be added to the Block Access List. |
385 | | -
|
386 | | - This test: |
387 | | - 1. Deploys a contract with SELFDESTRUCT bytecode |
388 | | - 2. Calls the contract with limited gas so SELFDESTRUCT fails OOG |
389 | | - 3. Verifies beneficiary is NOT in BAL (the CALL reverts, undoing BAL changes) |
390 | | -
|
391 | | - SELFDESTRUCT gas cost to cold new account: 5000 + 2600 + 25000 = 32600 gas |
392 | | - """ |
393 | | - alice = pre.fund_eoa() |
394 | | - |
395 | | - # Beneficiary address for SELFDESTRUCT |
396 | | - beneficiary = Address(0xBEEF) |
397 | | - |
398 | | - # Contract: PUSH20 <beneficiary> SELFDESTRUCT |
399 | | - selfdestruct_code = Op.SELFDESTRUCT(beneficiary) |
400 | | - selfdestruct_contract = pre.deploy_contract(code=selfdestruct_code, balance=1000) |
401 | | - |
402 | | - # Caller contract: CALL with limited gas to cause OOG on SELFDESTRUCT |
403 | | - # SELFDESTRUCT needs 32600 gas, we give it only 100 |
404 | | - caller_code = ( |
405 | | - Op.CALL(gas=100, address=selfdestruct_contract, value=0, |
406 | | - args_offset=0, args_size=0, ret_offset=0, ret_size=0) |
407 | | - + Op.STOP |
408 | | - ) |
409 | | - caller_contract = pre.deploy_contract(code=caller_code) |
410 | | - |
411 | | - tx = Transaction( |
412 | | - sender=alice, |
413 | | - to=caller_contract, |
414 | | - gas_limit=100_000, |
415 | | - gas_price=0xA, |
416 | | - ) |
417 | | - |
418 | | - # The inner CALL fails OOG, so SELFDESTRUCT doesn't complete. |
419 | | - # Beneficiary should NOT be in BAL. |
420 | | - block = Block( |
421 | | - txs=[tx], |
422 | | - expected_block_access_list=BlockAccessListExpectation( |
423 | | - account_expectations={ |
424 | | - alice: BalAccountExpectation( |
425 | | - nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)], |
426 | | - ), |
427 | | - caller_contract: BalAccountExpectation.empty(), |
428 | | - selfdestruct_contract: BalAccountExpectation.empty(), |
429 | | - # beneficiary should NOT appear - SELFDESTRUCT failed OOG |
430 | | - } |
431 | | - ), |
432 | | - ) |
433 | | - |
434 | | - blockchain_test( |
435 | | - pre=pre, |
436 | | - blocks=[block], |
437 | | - post={ |
438 | | - alice: Account(nonce=1), |
439 | | - caller_contract: Account(code=caller_code), |
440 | | - # Contract still exists - SELFDESTRUCT failed |
441 | | - selfdestruct_contract: Account(balance=1000, code=selfdestruct_code), |
442 | | - }, |
443 | | - ) |
444 | | - |
445 | | - |
446 | 219 | @pytest.mark.parametrize( |
447 | 220 | "account_access_opcode", |
448 | 221 | [ |
|
0 commit comments