From cb98281ef2a0d3b357870b52d9cf47f0508ccd01 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Sat, 29 Apr 2023 13:40:06 -0400 Subject: [PATCH 01/38] wip: test-case for eip-6780 --- fillers/eips/eip6780.py | 83 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 fillers/eips/eip6780.py diff --git a/fillers/eips/eip6780.py b/fillers/eips/eip6780.py new file mode 100644 index 0000000000..da9abde5e7 --- /dev/null +++ b/fillers/eips/eip6780.py @@ -0,0 +1,83 @@ +""" +Test EIP-6780: remove selfdestruct +""" +from typing import Dict + +from ethereum_test_forks import Shanghai, is_fork +from ethereum_test_tools import ( + Account, + CodeGasMeasure, + Environment, + StateTest, + TestAddress, + Transaction, + Yul, + test_from, + to_address, + to_hash, +) +from ethereum_test_tools.vm.opcode import Opcodes as Op + +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-3651.md" +REFERENCE_SPEC_VERSION = "cd7d6a465c03d86d852a1d6b5179bc78d760e658" + +@test_from(fork=Shanghai) +def test_eip6780_create_selfdestruct_same_tx(fork): + env = Environment( + coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + + test_ops =[ + "6032", + "601c", + "6000", + "39", # codecopy + # create account + "6032", + "6000", + "6000", # zero value + "f0" + # TODO call created account + "6000", + "6000", + "6000", + "6000", + "6000", + "85", # dup the returned address + "45", # pass gas limit + "f1", + "00" # STOP + # payload: + # copy inner payload to memory + "6008", + "600c", + "6000", + "39", # codecopy + # return payload + "6008", + "6000", + "f3", + # inner payload: + "60016000556000ff" + ] + + # post should have 0x5fef11c6545be552c986e9eaac3144ecf2258fd3 be empty account + + tx_create_code = "0x60016000556000ff" + pre = { + TestAddress: Account(balance=1000000000000000000000), + } + tx = Transaction( + ty=0x0, + data=test_ops, + chain_id=0x0, + nonce=0, + to="", + gas_limit=100000000, + gas_price=10, + protected=False, + ) From bed7f4e1d9055b9d4207a5c7e22933fdec8cff2a Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Sat, 29 Apr 2023 13:47:23 -0400 Subject: [PATCH 02/38] more --- fillers/eips/eip6780.py | 116 +++++++++++++++++++++------------------- 1 file changed, 62 insertions(+), 54 deletions(-) diff --git a/fillers/eips/eip6780.py b/fillers/eips/eip6780.py index da9abde5e7..d5cdd1d9c3 100644 --- a/fillers/eips/eip6780.py +++ b/fillers/eips/eip6780.py @@ -18,66 +18,74 @@ ) from ethereum_test_tools.vm.opcode import Opcodes as Op -REFERENCE_SPEC_GIT_PATH = "EIPS/eip-3651.md" +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6780.md" REFERENCE_SPEC_VERSION = "cd7d6a465c03d86d852a1d6b5179bc78d760e658" @test_from(fork=Shanghai) def test_eip6780_create_selfdestruct_same_tx(fork): - env = Environment( - coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", - difficulty=0x20000, - gas_limit=10000000000, - number=1, - timestamp=1000, - ) + env = Environment( + coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + post: Dict[Any, Any] = {} test_ops =[ - "6032", - "601c", - "6000", - "39", # codecopy - # create account - "6032", - "6000", - "6000", # zero value - "f0" - # TODO call created account - "6000", - "6000", - "6000", - "6000", - "6000", - "85", # dup the returned address - "45", # pass gas limit - "f1", - "00" # STOP - # payload: - # copy inner payload to memory - "6008", - "600c", - "6000", - "39", # codecopy - # return payload - "6008", - "6000", - "f3", - # inner payload: - "60016000556000ff" + "6032", + "601c", + "6000", + "39", # codecopy + # create account + "6032", + "6000", + "6000", # zero value + "f0" + # TODO call created account + "6000", + "6000", + "6000", + "6000", + "6000", + "85", # dup the returned address + "45", # pass gas limit + "f1", + "00" # STOP + # payload: + # copy inner payload to memory + "6008", + "600c", + "6000", + "39", # codecopy + # return payload + "6008", + "6000", + "f3", + # inner payload: + "60016000556000ff" ] - # post should have 0x5fef11c6545be552c986e9eaac3144ecf2258fd3 be empty account + post['0x5fef11c6545be552c986e9eaac3144ecf2258fd3'] = Account.NONEXISTENT - tx_create_code = "0x60016000556000ff" - pre = { - TestAddress: Account(balance=1000000000000000000000), - } - tx = Transaction( - ty=0x0, - data=test_ops, - chain_id=0x0, - nonce=0, - to="", - gas_limit=100000000, - gas_price=10, - protected=False, - ) + pre = { + TestAddress: Account(balance=1000000000000000000000), + } + tx = Transaction( + ty=0x0, + data=test_ops, + chain_id=0x0, + nonce=0, + to="", + gas_limit=100000000, + gas_price=10, + protected=False, + ) + + yield StateTest( + env=env, + pre=pre, + post=post, + txs=[tx], + tag=f"{initcode.name}", + ) From 24035016946d6ebdb15dcded793e5500b66dcb62 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 1 May 2023 13:53:44 -0400 Subject: [PATCH 03/38] wip --- fillers/eips/eip6780.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/fillers/eips/eip6780.py b/fillers/eips/eip6780.py index d5cdd1d9c3..983bb9f658 100644 --- a/fillers/eips/eip6780.py +++ b/fillers/eips/eip6780.py @@ -21,6 +21,10 @@ REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6780.md" REFERENCE_SPEC_VERSION = "cd7d6a465c03d86d852a1d6b5179bc78d760e658" +@test_from(fork=Shanghai) +def test_eip6780_create_selfdestruct_same_tx(fork): + pass + @test_from(fork=Shanghai) def test_eip6780_create_selfdestruct_same_tx(fork): env = Environment( @@ -65,6 +69,7 @@ def test_eip6780_create_selfdestruct_same_tx(fork): # inner payload: "60016000556000ff" ] + test_ops = "".join(test_ops) post['0x5fef11c6545be552c986e9eaac3144ecf2258fd3'] = Account.NONEXISTENT @@ -76,7 +81,7 @@ def test_eip6780_create_selfdestruct_same_tx(fork): data=test_ops, chain_id=0x0, nonce=0, - to="", + to=None, gas_limit=100000000, gas_price=10, protected=False, @@ -87,5 +92,5 @@ def test_eip6780_create_selfdestruct_same_tx(fork): pre=pre, post=post, txs=[tx], - tag=f"{initcode.name}", + tag="6780", ) From 52895f2285bcccb24ef181afb5310d3adc33811f Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 1 May 2023 15:06:06 -0400 Subject: [PATCH 04/38] add second test --- fillers/eips/eip6780.py | 51 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/fillers/eips/eip6780.py b/fillers/eips/eip6780.py index 983bb9f658..96894402c8 100644 --- a/fillers/eips/eip6780.py +++ b/fillers/eips/eip6780.py @@ -21,10 +21,6 @@ REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6780.md" REFERENCE_SPEC_VERSION = "cd7d6a465c03d86d852a1d6b5179bc78d760e658" -@test_from(fork=Shanghai) -def test_eip6780_create_selfdestruct_same_tx(fork): - pass - @test_from(fork=Shanghai) def test_eip6780_create_selfdestruct_same_tx(fork): env = Environment( @@ -92,5 +88,50 @@ def test_eip6780_create_selfdestruct_same_tx(fork): pre=pre, post=post, txs=[tx], - tag="6780", + tag="6780-create-inside-tx", + ) + +@test_from(fork=Shanghai) +def test_eip6780_selfdestruct_prev_created(fork): + env = Environment( + coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + post: Dict[Any, Any] = {} + + post['0x1111111111111111111111111111111111111111'] = Account( + balance=0, + code="0x60016000556000ff" + ) + post['0x0000000000000000000000000000000000000000'] = Account( + balance=1 + ) + + pre = { + TestAddress: Account(balance=1000000000000000000000), + "0x1111111111111111111111111111111111111111": Account( + balance=1, + code="0x60016000556000ff" + ) + } + tx = Transaction( + ty=0x0, + data="", + chain_id=0x0, + nonce=0, + to="0x1111111111111111111111111111111111111111", + gas_limit=100000000, + gas_price=10, + protected=False, + ) + + yield StateTest( + env=env, + pre=pre, + post=post, + txs=[tx], + tag="6780-create-inside-tx", ) From 40fa1c2a238c73fd379b7126fae6fb7a3a27c330 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 1 May 2023 15:31:08 -0400 Subject: [PATCH 05/38] move 6780 to cancun --- fillers/eips/eip6780.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fillers/eips/eip6780.py b/fillers/eips/eip6780.py index 96894402c8..1c41ca288f 100644 --- a/fillers/eips/eip6780.py +++ b/fillers/eips/eip6780.py @@ -3,7 +3,7 @@ """ from typing import Dict -from ethereum_test_forks import Shanghai, is_fork +from ethereum_test_forks import Cancun, is_fork from ethereum_test_tools import ( Account, CodeGasMeasure, @@ -21,7 +21,7 @@ REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6780.md" REFERENCE_SPEC_VERSION = "cd7d6a465c03d86d852a1d6b5179bc78d760e658" -@test_from(fork=Shanghai) +@test_from(fork=Cancun) def test_eip6780_create_selfdestruct_same_tx(fork): env = Environment( coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", @@ -91,7 +91,7 @@ def test_eip6780_create_selfdestruct_same_tx(fork): tag="6780-create-inside-tx", ) -@test_from(fork=Shanghai) +@test_from(fork=Cancun) def test_eip6780_selfdestruct_prev_created(fork): env = Environment( coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", From 10c5c4fa04e374c8cbc6bef4bbe60646b92c7999 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Tue, 2 May 2023 16:15:25 -0400 Subject: [PATCH 06/38] try thing --- fillers/eips/eip6780.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fillers/eips/eip6780.py b/fillers/eips/eip6780.py index 1c41ca288f..877fe3704c 100644 --- a/fillers/eips/eip6780.py +++ b/fillers/eips/eip6780.py @@ -4,6 +4,8 @@ from typing import Dict from ethereum_test_forks import Cancun, is_fork +from ethereum_test_forks.forks.upcoming import Cancun + from ethereum_test_tools import ( Account, CodeGasMeasure, @@ -19,7 +21,7 @@ from ethereum_test_tools.vm.opcode import Opcodes as Op REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6780.md" -REFERENCE_SPEC_VERSION = "cd7d6a465c03d86d852a1d6b5179bc78d760e658" +REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" @test_from(fork=Cancun) def test_eip6780_create_selfdestruct_same_tx(fork): From bb4b85bf01ec2b941a881d97b1b95c17ff2fe471 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Wed, 3 May 2023 13:32:17 -0400 Subject: [PATCH 07/38] move eip6780 tests into separate folder --- fillers/eips/eip6780/__init__.py | 0 fillers/eips/{ => eip6780}/eip6780.py | 9 ++++----- 2 files changed, 4 insertions(+), 5 deletions(-) create mode 100644 fillers/eips/eip6780/__init__.py rename fillers/eips/{ => eip6780}/eip6780.py (95%) diff --git a/fillers/eips/eip6780/__init__.py b/fillers/eips/eip6780/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/fillers/eips/eip6780.py b/fillers/eips/eip6780/eip6780.py similarity index 95% rename from fillers/eips/eip6780.py rename to fillers/eips/eip6780/eip6780.py index 877fe3704c..894c8b9474 100644 --- a/fillers/eips/eip6780.py +++ b/fillers/eips/eip6780/eip6780.py @@ -3,8 +3,7 @@ """ from typing import Dict -from ethereum_test_forks import Cancun, is_fork -from ethereum_test_forks.forks.upcoming import Cancun +from ethereum_test_forks import ShanghaiToCancunAtTime15k from ethereum_test_tools import ( Account, @@ -14,7 +13,7 @@ TestAddress, Transaction, Yul, - test_from, + test_only, to_address, to_hash, ) @@ -23,7 +22,7 @@ REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6780.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" -@test_from(fork=Cancun) +@test_only(fork=ShanghaiToCancunAtTime15k) def test_eip6780_create_selfdestruct_same_tx(fork): env = Environment( coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", @@ -93,7 +92,7 @@ def test_eip6780_create_selfdestruct_same_tx(fork): tag="6780-create-inside-tx", ) -@test_from(fork=Cancun) +@test_only(fork=ShanghaiToCancunAtTime15k) def test_eip6780_selfdestruct_prev_created(fork): env = Environment( coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", From b6ce5367827eab23294d0d7672d9a66dd186f0cd Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Tue, 9 May 2023 15:30:54 -0400 Subject: [PATCH 08/38] fix test fork to cancun. correct name on test --- fillers/eips/eip6780/eip6780.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fillers/eips/eip6780/eip6780.py b/fillers/eips/eip6780/eip6780.py index 894c8b9474..dce5129ebc 100644 --- a/fillers/eips/eip6780/eip6780.py +++ b/fillers/eips/eip6780/eip6780.py @@ -3,7 +3,7 @@ """ from typing import Dict -from ethereum_test_forks import ShanghaiToCancunAtTime15k +from ethereum_test_forks import Cancun from ethereum_test_tools import ( Account, @@ -22,7 +22,7 @@ REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6780.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" -@test_only(fork=ShanghaiToCancunAtTime15k) +@test_only(fork=Cancun) def test_eip6780_create_selfdestruct_same_tx(fork): env = Environment( coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", @@ -92,7 +92,7 @@ def test_eip6780_create_selfdestruct_same_tx(fork): tag="6780-create-inside-tx", ) -@test_only(fork=ShanghaiToCancunAtTime15k) +@test_only(fork=Cancun) def test_eip6780_selfdestruct_prev_created(fork): env = Environment( coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", @@ -134,5 +134,5 @@ def test_eip6780_selfdestruct_prev_created(fork): pre=pre, post=post, txs=[tx], - tag="6780-create-inside-tx", + tag="6780-prev-created", ) From 57c85a21ec6bf3da0a566c777ab22a909d5c5973 Mon Sep 17 00:00:00 2001 From: danceratopz Date: Thu, 6 Jul 2023 10:19:50 +0200 Subject: [PATCH 09/38] chore: move 6780 tests to a home in the new dir structure --- .../eip6780 => tests/cancun/eip6780_selfdestruct}/__init__.py | 0 .../cancun/eip6780_selfdestruct/test_selfdestruct.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {fillers/eips/eip6780 => tests/cancun/eip6780_selfdestruct}/__init__.py (100%) rename fillers/eips/eip6780/eip6780.py => tests/cancun/eip6780_selfdestruct/test_selfdestruct.py (100%) diff --git a/fillers/eips/eip6780/__init__.py b/tests/cancun/eip6780_selfdestruct/__init__.py similarity index 100% rename from fillers/eips/eip6780/__init__.py rename to tests/cancun/eip6780_selfdestruct/__init__.py diff --git a/fillers/eips/eip6780/eip6780.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py similarity index 100% rename from fillers/eips/eip6780/eip6780.py rename to tests/cancun/eip6780_selfdestruct/test_selfdestruct.py From 08eb712c6442d401690a84b6583363c4103bfb64 Mon Sep 17 00:00:00 2001 From: danceratopz Date: Thu, 6 Jul 2023 11:07:26 +0200 Subject: [PATCH 10/38] refactor: port 6780 tests to pytest format and lint/typecheck --- tests/cancun/eip6780_selfdestruct/__init__.py | 3 + .../eip6780_selfdestruct/test_selfdestruct.py | 250 ++++++++---------- 2 files changed, 120 insertions(+), 133 deletions(-) diff --git a/tests/cancun/eip6780_selfdestruct/__init__.py b/tests/cancun/eip6780_selfdestruct/__init__.py index e69de29bb2..a6cf378c65 100644 --- a/tests/cancun/eip6780_selfdestruct/__init__.py +++ b/tests/cancun/eip6780_selfdestruct/__init__.py @@ -0,0 +1,3 @@ +""" +Tests for EIP-6780: SELFDESTRUCT only in same transaction. +""" diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index dce5129ebc..fd1fe33ba0 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -1,138 +1,122 @@ """ -Test EIP-6780: remove selfdestruct -""" -from typing import Dict - -from ethereum_test_forks import Cancun - -from ethereum_test_tools import ( - Account, - CodeGasMeasure, - Environment, - StateTest, - TestAddress, - Transaction, - Yul, - test_only, - to_address, - to_hash, -) -from ethereum_test_tools.vm.opcode import Opcodes as Op +abstract: Tests [EIP-6780: SELFDESTRUCT only in same transaction](https://eips.ethereum.org/EIPS/eip-6780) + + Tests for [EIP-6780: SELFDESTRUCT only in same transaction](https://eips.ethereum.org/EIPS/eip-6780). + +""" # noqa: E501 + +from typing import Any, Dict + +import pytest + +from ethereum_test_tools import Account, Environment, StateTestFiller, TestAddress, Transaction REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6780.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" -@test_only(fork=Cancun) -def test_eip6780_create_selfdestruct_same_tx(fork): - env = Environment( - coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", - difficulty=0x20000, - gas_limit=10000000000, - number=1, - timestamp=1000, - ) - post: Dict[Any, Any] = {} - - test_ops =[ - "6032", - "601c", - "6000", - "39", # codecopy - # create account - "6032", - "6000", - "6000", # zero value - "f0" - # TODO call created account - "6000", - "6000", - "6000", - "6000", - "6000", - "85", # dup the returned address - "45", # pass gas limit - "f1", - "00" # STOP - # payload: - # copy inner payload to memory - "6008", - "600c", - "6000", - "39", # codecopy - # return payload - "6008", - "6000", - "f3", - # inner payload: - "60016000556000ff" - ] - test_ops = "".join(test_ops) - - post['0x5fef11c6545be552c986e9eaac3144ecf2258fd3'] = Account.NONEXISTENT - - pre = { - TestAddress: Account(balance=1000000000000000000000), - } - tx = Transaction( - ty=0x0, - data=test_ops, - chain_id=0x0, - nonce=0, - to=None, - gas_limit=100000000, - gas_price=10, - protected=False, - ) - - yield StateTest( - env=env, - pre=pre, - post=post, - txs=[tx], - tag="6780-create-inside-tx", - ) - -@test_only(fork=Cancun) -def test_eip6780_selfdestruct_prev_created(fork): - env = Environment( - coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", - difficulty=0x20000, - gas_limit=10000000000, - number=1, - timestamp=1000, - ) - post: Dict[Any, Any] = {} - - post['0x1111111111111111111111111111111111111111'] = Account( - balance=0, - code="0x60016000556000ff" - ) - post['0x0000000000000000000000000000000000000000'] = Account( - balance=1 - ) - - pre = { - TestAddress: Account(balance=1000000000000000000000), - "0x1111111111111111111111111111111111111111": Account( - balance=1, - code="0x60016000556000ff" - ) - } - tx = Transaction( - ty=0x0, - data="", - chain_id=0x0, - nonce=0, - to="0x1111111111111111111111111111111111111111", - gas_limit=100000000, - gas_price=10, - protected=False, - ) - - yield StateTest( - env=env, - pre=pre, - post=post, - txs=[tx], - tag="6780-prev-created", - ) + +@pytest.mark.valid_from("Shanghai") +def test_create_selfdestruct_same_tx(state_test: StateTestFiller): + """ + TODO Test selfdestruct in CREATE. + """ + env = Environment( + coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + post: Dict[Any, Any] = {} + + test_ops = [ + "6032", + "601c", + "6000", + "39", # codecopy + # create account + "6032", + "6000", + "6000", # zero value + "f0" + # TODO call created account + "6000", + "6000", + "6000", + "6000", + "6000", + "85", # dup the returned address + "45", # pass gas limit + "f1", + "00" # STOP + # payload: + # copy inner payload to memory + "6008", + "600c", + "6000", + "39", # codecopy + # return payload + "6008", + "6000", + "f3", + # inner payload: + "60016000556000ff", + ] + + post["0x5fef11c6545be552c986e9eaac3144ecf2258fd3"] = Account.NONEXISTENT + + pre = { + TestAddress: Account(balance=1000000000000000000000), + } + tx = Transaction( + ty=0x0, + data="".join(test_ops), + chain_id=0x0, + nonce=0, + to=None, + gas_limit=100000000, + gas_price=10, + protected=False, + ) + + state_test(env=env, pre=pre, post=post, txs=[tx], tag="6780-create-inside-tx") + + +@pytest.mark.xfail(run=True, reason="Unknown, tbd") +@pytest.mark.valid_from("Shanghai") +def test_selfdestruct_prev_created(state_test: StateTestFiller): + """ + TODO + """ + env = Environment( + coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + post: Dict[Any, Any] = {} + + post["0x1111111111111111111111111111111111111111"] = Account( + balance=0, code="0x60016000556000ff" + ) + post["0x0000000000000000000000000000000000000000"] = Account(balance=1) + + pre = { + TestAddress: Account(balance=1000000000000000000000), + "0x1111111111111111111111111111111111111111": Account( + balance=1, code="0x60016000556000ff" + ), + } + tx = Transaction( + ty=0x0, + data="", + chain_id=0x0, + nonce=0, + to="0x1111111111111111111111111111111111111111", + gas_limit=100000000, + gas_price=10, + protected=False, + ) + + state_test(env=env, pre=pre, post=post, txs=[tx], tag="6780-prev-created") From b0726c403b868b1b57e32ae112699f5b93088ac1 Mon Sep 17 00:00:00 2001 From: danceratopz Date: Thu, 6 Jul 2023 12:23:02 +0200 Subject: [PATCH 11/38] refactor: rewrite code using Opcodes library --- .../eip6780_selfdestruct/test_selfdestruct.py | 63 ++++++++++++++----- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index fd1fe33ba0..f487ab3331 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -10,6 +10,7 @@ import pytest from ethereum_test_tools import Account, Environment, StateTestFiller, TestAddress, Transaction +from ethereum_test_tools.vm.opcode import Opcodes as Op REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6780.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" @@ -18,7 +19,8 @@ @pytest.mark.valid_from("Shanghai") def test_create_selfdestruct_same_tx(state_test: StateTestFiller): """ - TODO Test selfdestruct in CREATE. + TODO test that if a contract is created and then selfdestructs in the same + transaction the contract should not be created. """ env = Environment( coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", @@ -29,6 +31,12 @@ def test_create_selfdestruct_same_tx(state_test: StateTestFiller): ) post: Dict[Any, Any] = {} + pre = { + TestAddress: Account(balance=1000000000000000000000), + } + + """ + original code test_ops = [ "6032", "601c", @@ -62,15 +70,36 @@ def test_create_selfdestruct_same_tx(state_test: StateTestFiller): # inner payload: "60016000556000ff", ] + """ + code = Op.CODECOPY(0, 0x1C, 0x32) + # create account + code += Op.CREATE(0, 0, 0x32) + # TODO call created account + code += Op.PUSH0 + code += Op.PUSH0 + code += Op.PUSH0 + code += Op.PUSH0 + code += Op.PUSH0 + code += Op.DUP6 # dup the returned address + code += Op.GASLIMIT + code += Op.CALL() + code += Op.STOP + # payload: + # copy inner payload to memory + code += Op.CODECOPY(0, 0x0C, 0x08) + # return payload + # inner payload: + code += Op.RETURN(0x00, 0x08) + code += Op.SSTORE(0, 1) + code += Op.PUSH0 + code += Op.SELFDESTRUCT post["0x5fef11c6545be552c986e9eaac3144ecf2258fd3"] = Account.NONEXISTENT - pre = { - TestAddress: Account(balance=1000000000000000000000), - } tx = Transaction( ty=0x0, - data="".join(test_ops), + # data="".join(test_ops), + data=code, chain_id=0x0, nonce=0, to=None, @@ -82,11 +111,14 @@ def test_create_selfdestruct_same_tx(state_test: StateTestFiller): state_test(env=env, pre=pre, post=post, txs=[tx], tag="6780-create-inside-tx") -@pytest.mark.xfail(run=True, reason="Unknown, tbd") +@pytest.mark.xfail( + run=True, reason="The account containing self-destruct is not present in the post-alloc." +) @pytest.mark.valid_from("Shanghai") def test_selfdestruct_prev_created(state_test: StateTestFiller): """ - TODO + Test that if a previously created account that contains a selfdestruct is + called, its balance is sent to the zero address. """ env = Environment( coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", @@ -95,19 +127,20 @@ def test_selfdestruct_prev_created(state_test: StateTestFiller): number=1, timestamp=1000, ) - post: Dict[Any, Any] = {} - post["0x1111111111111111111111111111111111111111"] = Account( - balance=0, code="0x60016000556000ff" - ) - post["0x0000000000000000000000000000000000000000"] = Account(balance=1) + # original code: 0x60016000556000ff # noqa: SC100 + code = Op.SSTORE(0, 1) + Op.PUSH0 + Op.SELFDESTRUCT pre = { TestAddress: Account(balance=1000000000000000000000), - "0x1111111111111111111111111111111111111111": Account( - balance=1, code="0x60016000556000ff" - ), + "0x1111111111111111111111111111111111111111": Account(balance=1, code=code), } + + post = { + "0x1111111111111111111111111111111111111111": Account(balance=0, code=code), + "0x0000000000000000000000000000000000000000": Account(balance=1), + } + tx = Transaction( ty=0x0, data="", From 20098d0636c064e3440599a9c65a15bc2960b5da Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Thu, 6 Jul 2023 16:57:27 +0000 Subject: [PATCH 12/38] tests/6780: eip enabled/disabled versions --- .../eip6780_selfdestruct/test_selfdestruct.py | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index f487ab3331..97a67aa2f0 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -5,7 +5,7 @@ """ # noqa: E501 -from typing import Any, Dict +from typing import Any, Dict, List import pytest @@ -15,7 +15,17 @@ REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6780.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" +SELFDESTRUCT_EIP_NUMBER = 6780 + +@pytest.mark.parametrize( + "eips", + [ + [SELFDESTRUCT_EIP_NUMBER], + [], + ], + ids=["eip-enabled", "eip-disabled"], +) @pytest.mark.valid_from("Shanghai") def test_create_selfdestruct_same_tx(state_test: StateTestFiller): """ @@ -111,15 +121,22 @@ def test_create_selfdestruct_same_tx(state_test: StateTestFiller): state_test(env=env, pre=pre, post=post, txs=[tx], tag="6780-create-inside-tx") -@pytest.mark.xfail( - run=True, reason="The account containing self-destruct is not present in the post-alloc." +@pytest.mark.parametrize( + "eips", + [ + [SELFDESTRUCT_EIP_NUMBER], + [], + ], + ids=["eip-enabled", "eip-disabled"], ) @pytest.mark.valid_from("Shanghai") -def test_selfdestruct_prev_created(state_test: StateTestFiller): +def test_selfdestruct_prev_created(state_test: StateTestFiller, eips: List[int]): """ Test that if a previously created account that contains a selfdestruct is called, its balance is sent to the zero address. """ + eip_enabled = SELFDESTRUCT_EIP_NUMBER in eips + env = Environment( coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", difficulty=0x20000, @@ -129,18 +146,22 @@ def test_selfdestruct_prev_created(state_test: StateTestFiller): ) # original code: 0x60016000556000ff # noqa: SC100 - code = Op.SSTORE(0, 1) + Op.PUSH0 + Op.SELFDESTRUCT + code = Op.SSTORE(0, 1) + Op.SELFDESTRUCT(0) pre = { TestAddress: Account(balance=1000000000000000000000), "0x1111111111111111111111111111111111111111": Account(balance=1, code=code), } - post = { - "0x1111111111111111111111111111111111111111": Account(balance=0, code=code), + post: Dict[str, Account] = { "0x0000000000000000000000000000000000000000": Account(balance=1), } + if eip_enabled: + post["0x1111111111111111111111111111111111111111"] = Account(balance=0, code=code) + else: + post["0x1111111111111111111111111111111111111111"] = Account.NONEXISTENT # type: ignore + tx = Transaction( ty=0x0, data="", From c5891706d0ddae92b5187f464f929547700718de Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Fri, 7 Jul 2023 00:26:01 +0000 Subject: [PATCH 13/38] tests/6780: major refactor --- .../eip6780_selfdestruct/test_selfdestruct.py | 476 ++++++++++++++---- 1 file changed, 386 insertions(+), 90 deletions(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index 97a67aa2f0..f1406e38d4 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -5,11 +5,26 @@ """ # noqa: E501 -from typing import Any, Dict, List +from itertools import count +from typing import Dict, List import pytest -from ethereum_test_tools import Account, Environment, StateTestFiller, TestAddress, Transaction +from ethereum_test_tools import ( + Account, + Block, + BlockchainTestFiller, + Environment, + Initcode, + StateTestFiller, + Storage, + TestAddress, + Transaction, + compute_create2_address, + compute_create_address, + to_address, + to_hash_bytes, +) from ethereum_test_tools.vm.opcode import Opcodes as Op REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6780.md" @@ -18,6 +33,56 @@ SELFDESTRUCT_EIP_NUMBER = 6780 +# TODO: + +# Destroy and re-create multiple times in the same tx +# Create and destroy multiple contracts in the same tx +# Create multiple contracts in a tx and destroy only one of them, but attempt to destroy the other one in a subsequent tx +# Create contract, attempt to destroy it in a subsequent tx in the same block +# Create a contract and try to destroy using another calltype (e.g. Delegatecall then destroy) +# Create a contract that creates another contract, then selfdestruct only the parent (or vice versa) +# Test selfdestructing using all types of create +# Create a contract using CREATE2, then in a subsequent tx do CREATE2 to the same address, and try to self-destruct +# Create a contract using CREATE2 to an account that has some balance, and try to self-destruct it in the same tx + + +@pytest.fixture +def env() -> Environment: + """Default environment for all tests.""" + return Environment( + coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + gas_limit=10000000000, + ) + + +@pytest.fixture +def sendall_destination_address() -> int: + """Account that receives the balance from the self-destructed account.""" + return 0x1234 + + +@pytest.fixture +def selfdestruct_code( + sendall_destination_address: int, +) -> bytes: + """Bytecode that self-destructs.""" + return Op.SSTORE( + 0, + Op.ADD(Op.SLOAD(0), 1), # Add to the SSTORE'd value each time we enter the contract + ) + Op.SELFDESTRUCT(sendall_destination_address) + + +@pytest.fixture +def selfdestruct_contract_initcode( + selfdestruct_code: bytes, +) -> bytes: + """Prepares an initcode that creates a self-destructing account.""" + return Initcode(deploy_code=selfdestruct_code).assemble() + + +@pytest.mark.parametrize("create_opcode", [Op.CREATE, Op.CREATE2]) +@pytest.mark.parametrize("call_times", [1, 40]) +@pytest.mark.parametrize("prefunded_selfdestruct_address", [False, True]) @pytest.mark.parametrize( "eips", [ @@ -27,100 +92,118 @@ ids=["eip-enabled", "eip-disabled"], ) @pytest.mark.valid_from("Shanghai") -def test_create_selfdestruct_same_tx(state_test: StateTestFiller): +def test_create_selfdestruct_same_tx( + state_test: StateTestFiller, + eips: List[int], + env: Environment, + selfdestruct_contract_initcode: bytes, + sendall_destination_address: int, + create_opcode: Op, + call_times: int, # Number of times to call the self-destructing contract in the same tx + prefunded_selfdestruct_address: bool, +): """ TODO test that if a contract is created and then selfdestructs in the same transaction the contract should not be created. """ - env = Environment( - coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", - difficulty=0x20000, - gas_limit=10000000000, - number=1, - timestamp=1000, + eip_enabled = SELFDESTRUCT_EIP_NUMBER in eips + + # Our entry point is an initcode that in turn creates a self-destructing contract + entry_code_address = compute_create_address(TestAddress, 0) + + # Address of a pre-existing contract we use to simply copy initcode from + selfdestruct_code_address = to_address(0x200) + + # Calculate the address of the selfdestructing contract + if create_opcode == Op.CREATE: + selfdestruct_contract_address = compute_create_address(entry_code_address, 1) + elif create_opcode == Op.CREATE2: + selfdestruct_contract_address = compute_create2_address( + entry_code_address, 0, selfdestruct_contract_initcode + ) + else: + raise Exception("Invalid opcode") + + # Bytecode used to create the contract, can be CREATE or CREATE2 + op_args = [ + 0, # Value + 0, # Offset + len(selfdestruct_contract_initcode), # Length + ] + if create_opcode == Op.CREATE2: + # CREATE2 requires a salt argument + op_args.append(0) + create_bytecode = create_opcode(*op_args) + + # Entry code that will be executed, creates the contract and then calls it in the same tx + entry_code = ( + # Initcode is already deployed at `selfdestruct_code_address`, so just copy it + Op.EXTCODECOPY( + Op.PUSH20(selfdestruct_code_address), + 0, + 0, + len(selfdestruct_contract_initcode), + ) + # And we store the created address for verification purposes + + Op.SSTORE( + 0, + create_bytecode, + ) ) - post: Dict[Any, Any] = {} + + sendall_amount = 0 + + # Call the self-destructing contract multiple times as required, incrementing the wei sent each + # time + for i in range(call_times): + entry_code += Op.CALL( + Op.GASLIMIT, # Gas + Op.PUSH20(selfdestruct_contract_address), # Address + i, # Value + 0, + 0, + 0, + 0, + ) + + # Lastly return "0x00" so the entry point contract is created and we can retain the stored + # values for verification. + entry_code += Op.RETURN(len(selfdestruct_contract_initcode), 1) pre = { TestAddress: Account(balance=1000000000000000000000), + selfdestruct_code_address: Account(code=selfdestruct_contract_initcode), } + if prefunded_selfdestruct_address: + pre[selfdestruct_contract_address] = Account(balance=1000000000000000000000) + sendall_amount += 1000000000000000000000 - """ - original code - test_ops = [ - "6032", - "601c", - "6000", - "39", # codecopy - # create account - "6032", - "6000", - "6000", # zero value - "f0" - # TODO call created account - "6000", - "6000", - "6000", - "6000", - "6000", - "85", # dup the returned address - "45", # pass gas limit - "f1", - "00" # STOP - # payload: - # copy inner payload to memory - "6008", - "600c", - "6000", - "39", # codecopy - # return payload - "6008", - "6000", - "f3", - # inner payload: - "60016000556000ff", - ] - """ - code = Op.CODECOPY(0, 0x1C, 0x32) - # create account - code += Op.CREATE(0, 0, 0x32) - # TODO call created account - code += Op.PUSH0 - code += Op.PUSH0 - code += Op.PUSH0 - code += Op.PUSH0 - code += Op.PUSH0 - code += Op.DUP6 # dup the returned address - code += Op.GASLIMIT - code += Op.CALL() - code += Op.STOP - # payload: - # copy inner payload to memory - code += Op.CODECOPY(0, 0x0C, 0x08) - # return payload - # inner payload: - code += Op.RETURN(0x00, 0x08) - code += Op.SSTORE(0, 1) - code += Op.PUSH0 - code += Op.SELFDESTRUCT - - post["0x5fef11c6545be552c986e9eaac3144ecf2258fd3"] = Account.NONEXISTENT + post: Dict[str, Account] = { + entry_code_address: Account(code="0x00", storage={0: selfdestruct_contract_address}), + selfdestruct_contract_address: Account.NONEXISTENT, # type: ignore + selfdestruct_code_address: Account(code=selfdestruct_contract_initcode), + } + if sendall_amount > 0: + post[to_address(sendall_destination_address)] = Account(balance=sendall_amount) + nonce = count() tx = Transaction( ty=0x0, - # data="".join(test_ops), - data=code, + data=entry_code, chain_id=0x0, - nonce=0, + nonce=next(nonce), to=None, gas_limit=100000000, gas_price=10, protected=False, ) - state_test(env=env, pre=pre, post=post, txs=[tx], tag="6780-create-inside-tx") + state_test(env=env, pre=pre, post=post, txs=[tx]) +@pytest.mark.parametrize("create_opcode", [Op.CREATE2]) # Can only recreate using CREATE2 +@pytest.mark.parametrize("recreate_times", [1]) +@pytest.mark.parametrize("call_times", [1]) @pytest.mark.parametrize( "eips", [ @@ -130,47 +213,260 @@ def test_create_selfdestruct_same_tx(state_test: StateTestFiller): ids=["eip-enabled", "eip-disabled"], ) @pytest.mark.valid_from("Shanghai") -def test_selfdestruct_prev_created(state_test: StateTestFiller, eips: List[int]): +def test_recreate_selfdestructed_contract( + blockchain_test: BlockchainTestFiller, + env: Environment, + selfdestruct_contract_initcode: bytes, + create_opcode: Op, + recreate_times: int, # Number of times to recreate the contract in different transactions + call_times: int, # Number of times to call the self-destructing contract in the same tx +): + """ + TODO test that if a contract is created and then selfdestructs in the same + transaction the contract should not be created. + """ + assert create_opcode == Op.CREATE2, "cannot recreate contract using CREATE opcode" + + entry_code_address = to_address( + 0x100 + ) # Needs to be constant to be able to recreate the contract + selfdestruct_code_address = to_address(0x200) + + # Calculate the address of the selfdestructing contract + if create_opcode == Op.CREATE: + selfdestruct_contract_address = compute_create_address(entry_code_address, 1) + elif create_opcode == Op.CREATE2: + selfdestruct_contract_address = compute_create2_address( + entry_code_address, 0, selfdestruct_contract_initcode + ) + else: + raise Exception("Invalid opcode") + + # Bytecode used to create the contract + op_args = [0, 0, len(selfdestruct_contract_initcode)] + if create_opcode == Op.CREATE2: + op_args.append(0) + + create_bytecode = create_opcode(*op_args) + + # Entry code that will be executed, creates the contract and then calls it + entry_code = ( + # Initcode is already deployed at selfdestruct_code_address, so just copy it + Op.EXTCODECOPY( + Op.PUSH20(selfdestruct_code_address), + 0, + 0, + len(selfdestruct_contract_initcode), + ) + + Op.SSTORE( + Op.CALLDATALOAD(0), + create_bytecode, + ) + ) + + for i in range(call_times): + entry_code += Op.CALL( + Op.GASLIMIT, + Op.PUSH20(selfdestruct_contract_address), + i, + 0, + 0, + 0, + 0, + ) + + entry_code += Op.RETURN(len(selfdestruct_contract_initcode), 1) + + pre = { + TestAddress: Account(balance=1000000000000000000000), + entry_code_address: Account(code=entry_code), + selfdestruct_code_address: Account(code=selfdestruct_contract_initcode), + } + + entry_code_storage: Storage.StorageDictType = {} + + txs: List[Transaction] = [] + nonce = count() + for i in range(recreate_times + 1): + txs.append( + Transaction( + ty=0x0, + data=to_hash_bytes(i), + chain_id=0x0, + nonce=next(nonce), + to=entry_code_address, + gas_limit=100000000, + gas_price=10, + protected=False, + ) + ) + entry_code_storage[i] = selfdestruct_contract_address + + entry_address_expected_code = entry_code + + post: Dict[str, Account] = { + entry_code_address: Account(code=entry_address_expected_code, storage=entry_code_storage), + selfdestruct_contract_address: Account.NONEXISTENT, # type: ignore + selfdestruct_code_address: Account(code=selfdestruct_contract_initcode), + } + + blockchain_test(genesis_environment=env, pre=pre, post=post, blocks=[Block(txs=txs)]) + + +@pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 1]) +@pytest.mark.parametrize( + "eips", + [ + [SELFDESTRUCT_EIP_NUMBER], + [], + ], + ids=["eip-enabled", "eip-disabled"], +) +@pytest.mark.valid_from("Shanghai") +def test_selfdestruct_prev_created( + state_test: StateTestFiller, + eips: List[int], + env: Environment, + selfdestruct_contract_initial_balance: int, +): """ Test that if a previously created account that contains a selfdestruct is called, its balance is sent to the zero address. """ eip_enabled = SELFDESTRUCT_EIP_NUMBER in eips - env = Environment( - coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", - difficulty=0x20000, - gas_limit=10000000000, - number=1, - timestamp=1000, - ) - # original code: 0x60016000556000ff # noqa: SC100 - code = Op.SSTORE(0, 1) + Op.SELFDESTRUCT(0) + balance_destination_address = 0x200 + code = Op.SSTORE(0, 1) + Op.SELFDESTRUCT(balance_destination_address) + selfdestruct_contract_address = "0x1111111111111111111111111111111111111111" pre = { TestAddress: Account(balance=1000000000000000000000), - "0x1111111111111111111111111111111111111111": Account(balance=1, code=code), + selfdestruct_contract_address: Account( + balance=selfdestruct_contract_initial_balance, code=code + ), + to_address(balance_destination_address): Account(balance=1), } post: Dict[str, Account] = { - "0x0000000000000000000000000000000000000000": Account(balance=1), + to_address(balance_destination_address): Account( + balance=selfdestruct_contract_initial_balance + 1 + ), } if eip_enabled: - post["0x1111111111111111111111111111111111111111"] = Account(balance=0, code=code) + post[selfdestruct_contract_address] = Account(balance=0, code=code) else: - post["0x1111111111111111111111111111111111111111"] = Account.NONEXISTENT # type: ignore + post[selfdestruct_contract_address] = Account.NONEXISTENT # type: ignore tx = Transaction( ty=0x0, data="", chain_id=0x0, nonce=0, - to="0x1111111111111111111111111111111111111111", + to=selfdestruct_contract_address, gas_limit=100000000, gas_price=10, protected=False, ) - state_test(env=env, pre=pre, post=post, txs=[tx], tag="6780-prev-created") + state_test(env=env, pre=pre, post=post, txs=[tx]) + + +@pytest.mark.parametrize( + "selfdestruct_contract_initial_balance,prefunding_tx", + [ + (0, False), + (1, False), + (1, True), + ], + ids=[ + "no-prefund", + "prefunded-in-selfdestruct-tx", + "prefunded-in-tx-before-create-tx", + ], +) +@pytest.mark.parametrize( + "eips", + [ + [SELFDESTRUCT_EIP_NUMBER], + [], + ], + ids=["eip-enabled", "eip-disabled"], +) +@pytest.mark.valid_from("Shanghai") +def test_selfdestruct_prev_created_same_block( + blockchain_test: BlockchainTestFiller, + eips: List[int], + env: Environment, + selfdestruct_code: bytes, + prefunding_tx: bool, + selfdestruct_contract_initcode: bytes, + selfdestruct_contract_initial_balance: int, + sendall_destination_address: int, +): + """ + Test that if an account created in the same block that contains a selfdestruct is + called, its balance is sent to the zero address. + """ + eip_enabled = SELFDESTRUCT_EIP_NUMBER in eips + + selfdestruct_contract_address = compute_create_address(TestAddress, 1 if prefunding_tx else 0) + + pre = { + TestAddress: Account(balance=1000000000000000000000), + to_address(sendall_destination_address): Account(balance=1), + } + + post: Dict[str, Account] = { + to_address(sendall_destination_address): Account( + balance=selfdestruct_contract_initial_balance + 1 + ), + } + + if eip_enabled: + post[selfdestruct_contract_address] = Account(balance=0, code=selfdestruct_code) + else: + post[selfdestruct_contract_address] = Account.NONEXISTENT # type: ignore + + txs: List[Transaction] = [] + nonce = count() + if prefunding_tx: + # Send some balance to the account to be created before it is created + txs += [ + Transaction( + ty=0x0, + chain_id=0x0, + value=selfdestruct_contract_initial_balance, + nonce=next(nonce), + to=selfdestruct_contract_address, + gas_limit=100000000, + gas_price=10, + protected=False, + ) + ] + txs += [ + Transaction( + ty=0x0, + data=selfdestruct_contract_initcode, + chain_id=0x0, + value=0, + nonce=next(nonce), + to=None, + gas_limit=100000000, + gas_price=10, + protected=False, + ), + Transaction( + ty=0x0, + chain_id=0x0, + value=selfdestruct_contract_initial_balance if not prefunding_tx else 0, + nonce=next(nonce), + to=selfdestruct_contract_address, + gas_limit=100000000, + gas_price=10, + protected=False, + ), + ] + + blockchain_test(genesis_environment=env, pre=pre, post=post, blocks=[Block(txs=txs)]) From d4732886cb84a9c1945781cd1cc388797ea1ec23 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Fri, 7 Jul 2023 00:36:08 +0000 Subject: [PATCH 14/38] tests/6780: cleaner eip enabled flag --- .../eip6780_selfdestruct/test_selfdestruct.py | 72 +++++++------------ 1 file changed, 25 insertions(+), 47 deletions(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index f1406e38d4..64b31443d9 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -35,15 +35,25 @@ # TODO: -# Destroy and re-create multiple times in the same tx -# Create and destroy multiple contracts in the same tx -# Create multiple contracts in a tx and destroy only one of them, but attempt to destroy the other one in a subsequent tx -# Create contract, attempt to destroy it in a subsequent tx in the same block -# Create a contract and try to destroy using another calltype (e.g. Delegatecall then destroy) -# Create a contract that creates another contract, then selfdestruct only the parent (or vice versa) -# Test selfdestructing using all types of create -# Create a contract using CREATE2, then in a subsequent tx do CREATE2 to the same address, and try to self-destruct -# Create a contract using CREATE2 to an account that has some balance, and try to self-destruct it in the same tx +# - Destroy and re-create multiple times in the same tx +# - Create and destroy multiple contracts in the same tx +# - Create multiple contracts in a tx and destroy only one of them, but attempt to destroy the +# other one in a subsequent tx +# - Create contract, attempt to destroy it in a subsequent tx in the same block +# - Create a contract and try to destroy using another calltype (e.g. Delegatecall then destroy) +# - Create a contract that creates another contract, then selfdestruct only the parent +# (or vice versa) +# - Test selfdestructing using all types of create +# - Create a contract using CREATE2, then in a subsequent tx do CREATE2 to the same address, and +# try to self-destruct +# - Create a contract using CREATE2 to an account that has some balance, and try to self-destruct +# it in the same tx + + +@pytest.fixture +def eips(eip_enabled: bool) -> List[int]: + """Prepares the list of EIPs depending on the test that enables it or not.""" + return [SELFDESTRUCT_EIP_NUMBER] if eip_enabled else [] @pytest.fixture @@ -83,18 +93,10 @@ def selfdestruct_contract_initcode( @pytest.mark.parametrize("create_opcode", [Op.CREATE, Op.CREATE2]) @pytest.mark.parametrize("call_times", [1, 40]) @pytest.mark.parametrize("prefunded_selfdestruct_address", [False, True]) -@pytest.mark.parametrize( - "eips", - [ - [SELFDESTRUCT_EIP_NUMBER], - [], - ], - ids=["eip-enabled", "eip-disabled"], -) +@pytest.mark.parametrize("eip_enabled", [True, False]) @pytest.mark.valid_from("Shanghai") def test_create_selfdestruct_same_tx( state_test: StateTestFiller, - eips: List[int], env: Environment, selfdestruct_contract_initcode: bytes, sendall_destination_address: int, @@ -106,7 +108,6 @@ def test_create_selfdestruct_same_tx( TODO test that if a contract is created and then selfdestructs in the same transaction the contract should not be created. """ - eip_enabled = SELFDESTRUCT_EIP_NUMBER in eips # Our entry point is an initcode that in turn creates a self-destructing contract entry_code_address = compute_create_address(TestAddress, 0) @@ -204,14 +205,7 @@ def test_create_selfdestruct_same_tx( @pytest.mark.parametrize("create_opcode", [Op.CREATE2]) # Can only recreate using CREATE2 @pytest.mark.parametrize("recreate_times", [1]) @pytest.mark.parametrize("call_times", [1]) -@pytest.mark.parametrize( - "eips", - [ - [SELFDESTRUCT_EIP_NUMBER], - [], - ], - ids=["eip-enabled", "eip-disabled"], -) +@pytest.mark.parametrize("eip_enabled", [True, False]) @pytest.mark.valid_from("Shanghai") def test_recreate_selfdestructed_contract( blockchain_test: BlockchainTestFiller, @@ -314,18 +308,11 @@ def test_recreate_selfdestructed_contract( @pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 1]) -@pytest.mark.parametrize( - "eips", - [ - [SELFDESTRUCT_EIP_NUMBER], - [], - ], - ids=["eip-enabled", "eip-disabled"], -) +@pytest.mark.parametrize("eip_enabled", [True, False]) @pytest.mark.valid_from("Shanghai") def test_selfdestruct_prev_created( state_test: StateTestFiller, - eips: List[int], + eip_enabled: bool, env: Environment, selfdestruct_contract_initial_balance: int, ): @@ -333,7 +320,6 @@ def test_selfdestruct_prev_created( Test that if a previously created account that contains a selfdestruct is called, its balance is sent to the zero address. """ - eip_enabled = SELFDESTRUCT_EIP_NUMBER in eips # original code: 0x60016000556000ff # noqa: SC100 balance_destination_address = 0x200 @@ -386,18 +372,11 @@ def test_selfdestruct_prev_created( "prefunded-in-tx-before-create-tx", ], ) -@pytest.mark.parametrize( - "eips", - [ - [SELFDESTRUCT_EIP_NUMBER], - [], - ], - ids=["eip-enabled", "eip-disabled"], -) +@pytest.mark.parametrize("eip_enabled", [True, False]) @pytest.mark.valid_from("Shanghai") def test_selfdestruct_prev_created_same_block( blockchain_test: BlockchainTestFiller, - eips: List[int], + eip_enabled: bool, env: Environment, selfdestruct_code: bytes, prefunding_tx: bool, @@ -409,7 +388,6 @@ def test_selfdestruct_prev_created_same_block( Test that if an account created in the same block that contains a selfdestruct is called, its balance is sent to the zero address. """ - eip_enabled = SELFDESTRUCT_EIP_NUMBER in eips selfdestruct_contract_address = compute_create_address(TestAddress, 1 if prefunding_tx else 0) From bd5e10e9c4927435a9056bf7eaad4cd8413a301d Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Fri, 7 Jul 2023 00:40:54 +0000 Subject: [PATCH 15/38] tests/6780: add storage check after sendall --- tests/cancun/eip6780_selfdestruct/test_selfdestruct.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index 64b31443d9..bf51be68c0 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -341,7 +341,7 @@ def test_selfdestruct_prev_created( } if eip_enabled: - post[selfdestruct_contract_address] = Account(balance=0, code=code) + post[selfdestruct_contract_address] = Account(balance=0, code=code, storage={0: 1}) else: post[selfdestruct_contract_address] = Account.NONEXISTENT # type: ignore From 5f104af70bdbfa589d4ff88c4369e14b03e27385 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Fri, 7 Jul 2023 00:49:05 +0000 Subject: [PATCH 16/38] nit: remove comment --- tests/cancun/eip6780_selfdestruct/test_selfdestruct.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index bf51be68c0..9ff6c1cf20 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -215,10 +215,6 @@ def test_recreate_selfdestructed_contract( recreate_times: int, # Number of times to recreate the contract in different transactions call_times: int, # Number of times to call the self-destructing contract in the same tx ): - """ - TODO test that if a contract is created and then selfdestructs in the same - transaction the contract should not be created. - """ assert create_opcode == Op.CREATE2, "cannot recreate contract using CREATE opcode" entry_code_address = to_address( From 4add31d85e5cf7890c5f4126cc938085306b994b Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Fri, 7 Jul 2023 19:55:47 +0000 Subject: [PATCH 17/38] tests/6780: more checks --- .../eip6780_selfdestruct/test_selfdestruct.py | 250 ++++++++++++++++-- 1 file changed, 226 insertions(+), 24 deletions(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index 9ff6c1cf20..e0bcb1ecc2 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -9,6 +9,7 @@ from typing import Dict, List import pytest +from ethereum.crypto.hash import keccak256 from ethereum_test_tools import ( Account, @@ -48,6 +49,8 @@ # try to self-destruct # - Create a contract using CREATE2 to an account that has some balance, and try to self-destruct # it in the same tx +# - Check balance after self-destruct using Op.BALANCE +# - Check that the Op.SENDALL recipient does not execute code @pytest.fixture @@ -82,11 +85,23 @@ def selfdestruct_code( ) + Op.SELFDESTRUCT(sendall_destination_address) +@pytest.fixture +def selfdestructing_initcode() -> bool: + """ + Whether the contract shall self-destruct during initialization. + By default it does not. + """ + return False + + @pytest.fixture def selfdestruct_contract_initcode( selfdestruct_code: bytes, + selfdestructing_initcode: bool, ) -> bytes: """Prepares an initcode that creates a self-destructing account.""" + if selfdestructing_initcode: + return selfdestruct_code return Initcode(deploy_code=selfdestruct_code).assemble() @@ -98,22 +113,21 @@ def selfdestruct_contract_initcode( def test_create_selfdestruct_same_tx( state_test: StateTestFiller, env: Environment, + selfdestruct_code: bytes, selfdestruct_contract_initcode: bytes, sendall_destination_address: int, create_opcode: Op, call_times: int, # Number of times to call the self-destructing contract in the same tx prefunded_selfdestruct_address: bool, ): - """ - TODO test that if a contract is created and then selfdestructs in the same - transaction the contract should not be created. - """ - # Our entry point is an initcode that in turn creates a self-destructing contract entry_code_address = compute_create_address(TestAddress, 0) + entry_code_storage: Storage.StorageDictType = {} + storage_index = count() + sendall_amount = 0 # Address of a pre-existing contract we use to simply copy initcode from - selfdestruct_code_address = to_address(0x200) + initcode_copy_from_address = to_address(0x200) # Calculate the address of the selfdestructing contract if create_opcode == Op.CREATE: @@ -138,9 +152,9 @@ def test_create_selfdestruct_same_tx( # Entry code that will be executed, creates the contract and then calls it in the same tx entry_code = ( - # Initcode is already deployed at `selfdestruct_code_address`, so just copy it + # Initcode is already deployed at `initcode_copy_from_address`, so just copy it Op.EXTCODECOPY( - Op.PUSH20(selfdestruct_code_address), + Op.PUSH20(initcode_copy_from_address), 0, 0, len(selfdestruct_contract_initcode), @@ -151,21 +165,198 @@ def test_create_selfdestruct_same_tx( create_bytecode, ) ) + # Store the created address + entry_code_storage[next(storage_index)] = selfdestruct_contract_address + + # Store the extcode* properties of the created address + current_index = next(storage_index) + entry_code += Op.SSTORE( + current_index, + Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), + ) + entry_code_storage[current_index] = len(selfdestruct_code) - sendall_amount = 0 + current_index = next(storage_index) + entry_code += Op.SSTORE( + current_index, + Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), + ) + entry_code_storage[current_index] = bytes(keccak256(selfdestruct_code)) # Call the self-destructing contract multiple times as required, incrementing the wei sent each # time for i in range(call_times): - entry_code += Op.CALL( - Op.GASLIMIT, # Gas - Op.PUSH20(selfdestruct_contract_address), # Address - i, # Value - 0, + current_index = next(storage_index) + entry_code += Op.SSTORE( + current_index, + Op.CALL( + Op.GASLIMIT, # Gas + Op.PUSH20(selfdestruct_contract_address), # Address + i, # Value + 0, + 0, + 0, + 0, + ), + ) + entry_code_storage[current_index] = 1 + + # TODO: Why is this correct ?? + sendall_amount += i + + current_index = next(storage_index) + entry_code += Op.SSTORE( + current_index, + Op.BALANCE(Op.PUSH20(selfdestruct_contract_address)), + ) + entry_code_storage[current_index] = 0 + + # Lastly return "0x00" so the entry point contract is created and we can retain the stored + # values for verification. + entry_code += Op.RETURN(len(selfdestruct_contract_initcode), 1) + + pre = { + TestAddress: Account(balance=1000000000000000000000), + initcode_copy_from_address: Account(code=selfdestruct_contract_initcode), + } + + if prefunded_selfdestruct_address: + # Address where the contract is created already had some balance, + # which must be included in the send-all operation + pre[selfdestruct_contract_address] = Account(balance=1000000000000000000000) + sendall_amount += 1000000000000000000000 + + post: Dict[str, Account] = { + entry_code_address: Account( + code="0x00", + storage=entry_code_storage, + ), + selfdestruct_contract_address: Account.NONEXISTENT, # type: ignore + initcode_copy_from_address: Account( + code=selfdestruct_contract_initcode, + ), + } + if sendall_amount > 0: + post[to_address(sendall_destination_address)] = Account(balance=sendall_amount) + + nonce = count() + tx = Transaction( + ty=0x0, + value=100_000, + data=entry_code, + chain_id=0x0, + nonce=next(nonce), + to=None, + gas_limit=100000000, + gas_price=10, + protected=False, + ) + + state_test(env=env, pre=pre, post=post, txs=[tx]) + + +@pytest.mark.parametrize("create_opcode", [Op.CREATE, Op.CREATE2]) +@pytest.mark.parametrize("call_times", [0, 1]) +@pytest.mark.parametrize("prefunded_selfdestruct_address", [False, True]) +@pytest.mark.parametrize("selfdestructing_initcode", [True], ids=[""]) +@pytest.mark.parametrize("eip_enabled", [True, False]) +@pytest.mark.valid_from("Shanghai") +def test_selfdestructing_initcode( + state_test: StateTestFiller, + env: Environment, + selfdestruct_contract_initcode: bytes, + sendall_destination_address: int, + create_opcode: Op, + call_times: int, # Number of times to call the self-destructing contract in the same tx + prefunded_selfdestruct_address: bool, +): + # Our entry point is an initcode that in turn creates a self-destructing contract + entry_code_address = compute_create_address(TestAddress, 0) + entry_code_storage: Storage.StorageDictType = {} + storage_index = count() + sendall_amount = 0 + + # Address of a pre-existing contract we use to simply copy initcode from + initcode_copy_from_address = to_address(0x200) + + # Calculate the address of the selfdestructing contract + if create_opcode == Op.CREATE: + selfdestruct_contract_address = compute_create_address(entry_code_address, 1) + elif create_opcode == Op.CREATE2: + selfdestruct_contract_address = compute_create2_address( + entry_code_address, 0, selfdestruct_contract_initcode + ) + else: + raise Exception("Invalid opcode") + + # Bytecode used to create the contract, can be CREATE or CREATE2 + op_args = [ + 0, # Value + 0, # Offset + len(selfdestruct_contract_initcode), # Length + ] + if create_opcode == Op.CREATE2: + # CREATE2 requires a salt argument + op_args.append(0) + create_bytecode = create_opcode(*op_args) + + # 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( + Op.PUSH20(initcode_copy_from_address), 0, 0, + len(selfdestruct_contract_initcode), + ) + # And we store the created address for verification purposes + + Op.SSTORE( 0, + create_bytecode, + ) + ) + # Store the created address + entry_code_storage[next(storage_index)] = selfdestruct_contract_address + + # Store the extcode* properties of the created address + current_index = next(storage_index) + entry_code += Op.SSTORE( + current_index, + Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), + ) + entry_code_storage[current_index] = 0 + + current_index = next(storage_index) + entry_code += Op.SSTORE( + current_index, + Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), + ) + entry_code_storage[current_index] = bytes(keccak256(bytes())) + + # Call the self-destructing contract multiple times as required, incrementing the wei sent each + # time + for i in range(call_times): + current_index = next(storage_index) + entry_code += Op.SSTORE( + current_index, + Op.CALL( + Op.GASLIMIT, # Gas + Op.PUSH20(selfdestruct_contract_address), # Address + i, # Value + 0, + 0, + 0, + 0, + ), ) + entry_code_storage[current_index] = 1 + + current_index = next(storage_index) + entry_code += Op.SSTORE( + current_index, + Op.BALANCE(Op.PUSH20(selfdestruct_contract_address)), + ) + entry_code_storage[current_index] = 0 # Lastly return "0x00" so the entry point contract is created and we can retain the stored # values for verification. @@ -173,16 +364,24 @@ def test_create_selfdestruct_same_tx( pre = { TestAddress: Account(balance=1000000000000000000000), - selfdestruct_code_address: Account(code=selfdestruct_contract_initcode), + initcode_copy_from_address: Account(code=selfdestruct_contract_initcode), } + if prefunded_selfdestruct_address: + # Address where the contract is created already had some balance, + # which must be included in the send-all operation pre[selfdestruct_contract_address] = Account(balance=1000000000000000000000) sendall_amount += 1000000000000000000000 post: Dict[str, Account] = { - entry_code_address: Account(code="0x00", storage={0: selfdestruct_contract_address}), + entry_code_address: Account( + code="0x00", + storage=entry_code_storage, + ), selfdestruct_contract_address: Account.NONEXISTENT, # type: ignore - selfdestruct_code_address: Account(code=selfdestruct_contract_initcode), + initcode_copy_from_address: Account( + code=selfdestruct_contract_initcode, + ), } if sendall_amount > 0: post[to_address(sendall_destination_address)] = Account(balance=sendall_amount) @@ -190,6 +389,7 @@ def test_create_selfdestruct_same_tx( nonce = count() tx = Transaction( ty=0x0, + value=100_000, data=entry_code, chain_id=0x0, nonce=next(nonce), @@ -220,7 +420,9 @@ def test_recreate_selfdestructed_contract( entry_code_address = to_address( 0x100 ) # Needs to be constant to be able to recreate the contract - selfdestruct_code_address = to_address(0x200) + initcode_copy_from_address = to_address(0x200) + entry_code_storage: Storage.StorageDictType = {} + storage_index = count() # Calculate the address of the selfdestructing contract if create_opcode == Op.CREATE: @@ -241,9 +443,9 @@ def test_recreate_selfdestructed_contract( # Entry code that will be executed, creates the contract and then calls it entry_code = ( - # Initcode is already deployed at selfdestruct_code_address, so just copy it + # Initcode is already deployed at initcode_copy_from_address, so just copy it Op.EXTCODECOPY( - Op.PUSH20(selfdestruct_code_address), + Op.PUSH20(initcode_copy_from_address), 0, 0, len(selfdestruct_contract_initcode), @@ -264,17 +466,16 @@ def test_recreate_selfdestructed_contract( 0, 0, ) + # CHECK BALANCE HERE entry_code += Op.RETURN(len(selfdestruct_contract_initcode), 1) pre = { TestAddress: Account(balance=1000000000000000000000), entry_code_address: Account(code=entry_code), - selfdestruct_code_address: Account(code=selfdestruct_contract_initcode), + initcode_copy_from_address: Account(code=selfdestruct_contract_initcode), } - entry_code_storage: Storage.StorageDictType = {} - txs: List[Transaction] = [] nonce = count() for i in range(recreate_times + 1): @@ -297,7 +498,7 @@ def test_recreate_selfdestructed_contract( post: Dict[str, Account] = { entry_code_address: Account(code=entry_address_expected_code, storage=entry_code_storage), selfdestruct_contract_address: Account.NONEXISTENT, # type: ignore - selfdestruct_code_address: Account(code=selfdestruct_contract_initcode), + initcode_copy_from_address: Account(code=selfdestruct_contract_initcode), } blockchain_test(genesis_environment=env, pre=pre, post=post, blocks=[Block(txs=txs)]) @@ -329,6 +530,7 @@ def test_selfdestruct_prev_created( ), to_address(balance_destination_address): Account(balance=1), } + # CHECK BALANCE SOMEWHERE post: Dict[str, Account] = { to_address(balance_destination_address): Account( From 9bd4d6f0b2d2bd982eadb26772207f04b53eefbe Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Fri, 7 Jul 2023 19:56:22 +0000 Subject: [PATCH 18/38] tests/6780: extcode checks after selfdestruct --- .../eip6780_selfdestruct/test_selfdestruct.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index e0bcb1ecc2..46e83ca107 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -211,6 +211,23 @@ def test_create_selfdestruct_same_tx( ) entry_code_storage[current_index] = 0 + # Check the extcode* properties of the selfdestructing contract again + current_index = next(storage_index) + entry_code += Op.SSTORE( + current_index, + Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), + ) + entry_code_storage[current_index] = 0 if call_times > 0 else len(selfdestruct_code) + + current_index = next(storage_index) + entry_code += Op.SSTORE( + current_index, + Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), + ) + entry_code_storage[current_index] = bytes( + keccak256(selfdestruct_code if call_times > 0 else b"") + ) + # Lastly return "0x00" so the entry point contract is created and we can retain the stored # values for verification. entry_code += Op.RETURN(len(selfdestruct_contract_initcode), 1) From bc5ce6119a6d5f2e74514323b03b32ccdfb50785 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Sat, 8 Jul 2023 00:10:09 +0000 Subject: [PATCH 19/38] tests/6780: more refactoring and more tests --- .../eip6780_selfdestruct/test_selfdestruct.py | 369 ++++++++++++------ 1 file changed, 244 insertions(+), 125 deletions(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index 46e83ca107..f02bce6d68 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -33,6 +33,7 @@ SELFDESTRUCT_EIP_NUMBER = 6780 +PRE_EXISTING_SELFDESTRUCT_ADDRESS = "0x1111111111111111111111111111111111111111" # TODO: @@ -51,6 +52,8 @@ # it in the same tx # - Check balance after self-destruct using Op.BALANCE # - Check that the Op.SENDALL recipient does not execute code +# - Delegate call to a contract that contains self-destruct and was created in the current tx +# from a contract that was created in a previous tx @pytest.fixture @@ -64,7 +67,7 @@ def env() -> Environment: """Default environment for all tests.""" return Environment( coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", - gas_limit=10000000000, + gas_limit=10_000_000_000, ) @@ -105,20 +108,80 @@ def selfdestruct_contract_initcode( return Initcode(deploy_code=selfdestruct_code).assemble() +@pytest.fixture +def initcode_copy_from_address() -> str: + """Address of a pre-existing contract we use to simply copy initcode from.""" + return to_address(0xABCD) + + +@pytest.fixture +def entry_code_address() -> str: + """Address where the entry code will run.""" + return compute_create_address(TestAddress, 0) + + +@pytest.fixture +def selfdestruct_contract_address( + create_opcode: Op, + entry_code_address: str, + selfdestruct_contract_initcode: bytes, +) -> str: + """Returns the address of the self-destructing contract.""" + if create_opcode == Op.CREATE: + return compute_create_address(entry_code_address, 1) + + if create_opcode == Op.CREATE2: + return compute_create2_address(entry_code_address, 0, selfdestruct_contract_initcode) + + raise Exception("Invalid opcode") + + +@pytest.fixture +def pre( + initcode_copy_from_address: str, + selfdestruct_contract_initcode: bytes, + selfdestruct_code: bytes, + selfdestruct_contract_address: str, + selfdestruct_contract_initial_balance: int, +) -> Dict[str, Account]: + """Pre-state of all tests""" + pre = { + TestAddress: Account(balance=100_000_000_000_000_000_000), + initcode_copy_from_address: Account(code=selfdestruct_contract_initcode), + } + + if ( + selfdestruct_contract_initial_balance > 0 + and selfdestruct_contract_address != PRE_EXISTING_SELFDESTRUCT_ADDRESS + ): + pre[selfdestruct_contract_address] = Account(balance=selfdestruct_contract_initial_balance) + + # Also put a pre-existing copy of the self-destruct contract in a known place + pre[PRE_EXISTING_SELFDESTRUCT_ADDRESS] = Account( + code=selfdestruct_code, + balance=selfdestruct_contract_initial_balance, + ) + + return pre + + @pytest.mark.parametrize("create_opcode", [Op.CREATE, Op.CREATE2]) @pytest.mark.parametrize("call_times", [1, 40]) -@pytest.mark.parametrize("prefunded_selfdestruct_address", [False, True]) +@pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 100_000]) @pytest.mark.parametrize("eip_enabled", [True, False]) @pytest.mark.valid_from("Shanghai") def test_create_selfdestruct_same_tx( state_test: StateTestFiller, env: Environment, + pre: Dict[str, Account], selfdestruct_code: bytes, selfdestruct_contract_initcode: bytes, + selfdestruct_contract_address: str, sendall_destination_address: int, + initcode_copy_from_address: str, create_opcode: Op, - call_times: int, # Number of times to call the self-destructing contract in the same tx - prefunded_selfdestruct_address: bool, + call_times: int, + selfdestruct_contract_initial_balance: int, ): # Our entry point is an initcode that in turn creates a self-destructing contract entry_code_address = compute_create_address(TestAddress, 0) @@ -126,19 +189,6 @@ def test_create_selfdestruct_same_tx( storage_index = count() sendall_amount = 0 - # Address of a pre-existing contract we use to simply copy initcode from - initcode_copy_from_address = to_address(0x200) - - # Calculate the address of the selfdestructing contract - if create_opcode == Op.CREATE: - selfdestruct_contract_address = compute_create_address(entry_code_address, 1) - elif create_opcode == Op.CREATE2: - selfdestruct_contract_address = compute_create2_address( - entry_code_address, 0, selfdestruct_contract_initcode - ) - else: - raise Exception("Invalid opcode") - # Bytecode used to create the contract, can be CREATE or CREATE2 op_args = [ 0, # Value @@ -217,7 +267,7 @@ def test_create_selfdestruct_same_tx( current_index, Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), ) - entry_code_storage[current_index] = 0 if call_times > 0 else len(selfdestruct_code) + entry_code_storage[current_index] = len(selfdestruct_code) current_index = next(storage_index) entry_code += Op.SSTORE( @@ -232,16 +282,10 @@ def test_create_selfdestruct_same_tx( # values for verification. entry_code += Op.RETURN(len(selfdestruct_contract_initcode), 1) - pre = { - TestAddress: Account(balance=1000000000000000000000), - initcode_copy_from_address: Account(code=selfdestruct_contract_initcode), - } - - if prefunded_selfdestruct_address: + if selfdestruct_contract_initial_balance > 0: # Address where the contract is created already had some balance, # which must be included in the send-all operation - pre[selfdestruct_contract_address] = Account(balance=1000000000000000000000) - sendall_amount += 1000000000000000000000 + sendall_amount += selfdestruct_contract_initial_balance post: Dict[str, Account] = { entry_code_address: Account( @@ -264,7 +308,7 @@ def test_create_selfdestruct_same_tx( chain_id=0x0, nonce=next(nonce), to=None, - gas_limit=100000000, + gas_limit=100_000_000, gas_price=10, protected=False, ) @@ -274,18 +318,21 @@ def test_create_selfdestruct_same_tx( @pytest.mark.parametrize("create_opcode", [Op.CREATE, Op.CREATE2]) @pytest.mark.parametrize("call_times", [0, 1]) -@pytest.mark.parametrize("prefunded_selfdestruct_address", [False, True]) +@pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 100_000]) @pytest.mark.parametrize("selfdestructing_initcode", [True], ids=[""]) @pytest.mark.parametrize("eip_enabled", [True, False]) @pytest.mark.valid_from("Shanghai") def test_selfdestructing_initcode( state_test: StateTestFiller, env: Environment, + pre: Dict[str, Account], selfdestruct_contract_initcode: bytes, + selfdestruct_contract_address: str, sendall_destination_address: int, + initcode_copy_from_address: str, create_opcode: Op, call_times: int, # Number of times to call the self-destructing contract in the same tx - prefunded_selfdestruct_address: bool, + selfdestruct_contract_initial_balance: int, ): # Our entry point is an initcode that in turn creates a self-destructing contract entry_code_address = compute_create_address(TestAddress, 0) @@ -293,19 +340,6 @@ def test_selfdestructing_initcode( storage_index = count() sendall_amount = 0 - # Address of a pre-existing contract we use to simply copy initcode from - initcode_copy_from_address = to_address(0x200) - - # Calculate the address of the selfdestructing contract - if create_opcode == Op.CREATE: - selfdestruct_contract_address = compute_create_address(entry_code_address, 1) - elif create_opcode == Op.CREATE2: - selfdestruct_contract_address = compute_create2_address( - entry_code_address, 0, selfdestruct_contract_initcode - ) - else: - raise Exception("Invalid opcode") - # Bytecode used to create the contract, can be CREATE or CREATE2 op_args = [ 0, # Value @@ -379,16 +413,10 @@ def test_selfdestructing_initcode( # values for verification. entry_code += Op.RETURN(len(selfdestruct_contract_initcode), 1) - pre = { - TestAddress: Account(balance=1000000000000000000000), - initcode_copy_from_address: Account(code=selfdestruct_contract_initcode), - } - - if prefunded_selfdestruct_address: + if selfdestruct_contract_initial_balance > 0: # Address where the contract is created already had some balance, # which must be included in the send-all operation - pre[selfdestruct_contract_address] = Account(balance=1000000000000000000000) - sendall_amount += 1000000000000000000000 + sendall_amount += selfdestruct_contract_initial_balance post: Dict[str, Account] = { entry_code_address: Account( @@ -411,7 +439,7 @@ def test_selfdestructing_initcode( chain_id=0x0, nonce=next(nonce), to=None, - gas_limit=100000000, + gas_limit=100_000_000, gas_price=10, protected=False, ) @@ -439,7 +467,6 @@ def test_recreate_selfdestructed_contract( ) # Needs to be constant to be able to recreate the contract initcode_copy_from_address = to_address(0x200) entry_code_storage: Storage.StorageDictType = {} - storage_index = count() # Calculate the address of the selfdestructing contract if create_opcode == Op.CREATE: @@ -488,7 +515,7 @@ def test_recreate_selfdestructed_contract( entry_code += Op.RETURN(len(selfdestruct_contract_initcode), 1) pre = { - TestAddress: Account(balance=1000000000000000000000), + TestAddress: Account(balance=10_000_000_00000000000000), entry_code_address: Account(code=entry_code), initcode_copy_from_address: Account(code=selfdestruct_contract_initcode), } @@ -503,7 +530,7 @@ def test_recreate_selfdestructed_contract( chain_id=0x0, nonce=next(nonce), to=entry_code_address, - gas_limit=100000000, + gas_limit=100_000_000, gas_price=10, protected=False, ) @@ -522,51 +549,109 @@ def test_recreate_selfdestructed_contract( @pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 1]) +@pytest.mark.parametrize("call_times", [1, 10]) +@pytest.mark.parametrize("selfdestruct_contract_address", [PRE_EXISTING_SELFDESTRUCT_ADDRESS]) @pytest.mark.parametrize("eip_enabled", [True, False]) @pytest.mark.valid_from("Shanghai") -def test_selfdestruct_prev_created( +def test_selfdestruct_pre_existing( state_test: StateTestFiller, eip_enabled: bool, env: Environment, + pre: Dict[str, Account], + selfdestruct_contract_address: str, + selfdestruct_code: bytes, selfdestruct_contract_initial_balance: int, + sendall_destination_address: int, + call_times: int, ): """ Test that if a previously created account that contains a selfdestruct is - called, its balance is sent to the zero address. + called, its balance is sent to the destination address. """ + entry_code_address = compute_create_address(TestAddress, 0) + entry_code_storage: Storage.StorageDictType = {} + storage_index = count() + sendall_amount = selfdestruct_contract_initial_balance + entry_code = b"" - # original code: 0x60016000556000ff # noqa: SC100 - balance_destination_address = 0x200 - code = Op.SSTORE(0, 1) + Op.SELFDESTRUCT(balance_destination_address) + # Entry code in this case will simply call the pre-existing selfdestructing contract, + # as many times as required - selfdestruct_contract_address = "0x1111111111111111111111111111111111111111" - pre = { - TestAddress: Account(balance=1000000000000000000000), - selfdestruct_contract_address: Account( - balance=selfdestruct_contract_initial_balance, code=code - ), - to_address(balance_destination_address): Account(balance=1), - } - # CHECK BALANCE SOMEWHERE + # Call the self-destructing contract multiple times as required, incrementing the wei sent each + # time + for i in range(call_times): + current_index = next(storage_index) + entry_code += Op.SSTORE( + current_index, + Op.CALL( + Op.GASLIMIT, # Gas + Op.PUSH20(selfdestruct_contract_address), # Address + i, # Value + 0, + 0, + 0, + 0, + ), + ) + entry_code_storage[current_index] = 1 - post: Dict[str, Account] = { - to_address(balance_destination_address): Account( - balance=selfdestruct_contract_initial_balance + 1 - ), - } + sendall_amount += i + + current_index = next(storage_index) + entry_code += Op.SSTORE( + current_index, + Op.BALANCE(Op.PUSH20(selfdestruct_contract_address)), + ) + entry_code_storage[current_index] = 0 + + # Check the extcode* properties of the selfdestructing contract + current_index = next(storage_index) + entry_code += Op.SSTORE( + current_index, + Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), + ) + entry_code_storage[current_index] = len(selfdestruct_code) + + current_index = next(storage_index) + entry_code += Op.SSTORE( + current_index, + Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), + ) + # entry_code_storage[current_index] = bytes(keccak256(selfdestruct_code if eip_enabled else b"")) + # TODO: Don't really understand why this works. It should be empty if EIP is disabled, but it works if it's not + entry_code_storage[current_index] = bytes(keccak256(selfdestruct_code)) + + # Lastly return "0x00" so the entry point contract is created and we can retain the stored + # values for verification. + entry_code += Op.RETURN(0, 1) + + post: Dict[str, Account] = {} + if sendall_amount > 0: + post[to_address(sendall_destination_address)] = Account(balance=sendall_amount) + else: + post[to_address(sendall_destination_address)] = Account.NONEXISTENT # type: ignore if eip_enabled: - post[selfdestruct_contract_address] = Account(balance=0, code=code, storage={0: 1}) + post[selfdestruct_contract_address] = Account( + balance=0, code=selfdestruct_code, storage={0: call_times} + ) else: post[selfdestruct_contract_address] = Account.NONEXISTENT # type: ignore + post[entry_code_address] = Account( + code="0x00", + storage=entry_code_storage, + ) + + nonce = count() tx = Transaction( ty=0x0, - data="", + value=100_000, + data=entry_code, chain_id=0x0, - nonce=0, - to=selfdestruct_contract_address, - gas_limit=100000000, + nonce=next(nonce), + to=None, + gas_limit=100_000_000, gas_price=10, protected=False, ) @@ -574,89 +659,123 @@ def test_selfdestruct_prev_created( state_test(env=env, pre=pre, post=post, txs=[tx]) -@pytest.mark.parametrize( - "selfdestruct_contract_initial_balance,prefunding_tx", - [ - (0, False), - (1, False), - (1, True), - ], - ids=[ - "no-prefund", - "prefunded-in-selfdestruct-tx", - "prefunded-in-tx-before-create-tx", - ], -) +@pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 1]) +@pytest.mark.parametrize("call_times", [1, 10]) +@pytest.mark.parametrize("selfdestruct_contract_address", [compute_create_address(TestAddress, 0)]) @pytest.mark.parametrize("eip_enabled", [True, False]) @pytest.mark.valid_from("Shanghai") -def test_selfdestruct_prev_created_same_block( +def test_selfdestruct_created_same_block_different_tx( blockchain_test: BlockchainTestFiller, eip_enabled: bool, env: Environment, + pre: Dict[str, Account], + selfdestruct_contract_address: str, selfdestruct_code: bytes, - prefunding_tx: bool, selfdestruct_contract_initcode: bytes, selfdestruct_contract_initial_balance: int, sendall_destination_address: int, + call_times: int, ): """ Test that if an account created in the same block that contains a selfdestruct is called, its balance is sent to the zero address. """ + entry_code_address = compute_create_address(TestAddress, 1) + entry_code_storage: Storage.StorageDictType = {} + storage_index = count() + sendall_amount = selfdestruct_contract_initial_balance + entry_code = b"" - selfdestruct_contract_address = compute_create_address(TestAddress, 1 if prefunding_tx else 0) + # Entry code in this case will simply call the pre-existing selfdestructing contract, + # as many times as required - pre = { - TestAddress: Account(balance=1000000000000000000000), - to_address(sendall_destination_address): Account(balance=1), - } + # Call the self-destructing contract multiple times as required, incrementing the wei sent each + # time + for i in range(call_times): + current_index = next(storage_index) + entry_code += Op.SSTORE( + current_index, + Op.CALL( + Op.GASLIMIT, # Gas + Op.PUSH20(selfdestruct_contract_address), # Address + i, # Value + 0, + 0, + 0, + 0, + ), + ) + entry_code_storage[current_index] = 1 - post: Dict[str, Account] = { - to_address(sendall_destination_address): Account( - balance=selfdestruct_contract_initial_balance + 1 - ), - } + sendall_amount += i + + current_index = next(storage_index) + entry_code += Op.SSTORE( + current_index, + Op.BALANCE(Op.PUSH20(selfdestruct_contract_address)), + ) + entry_code_storage[current_index] = 0 + + # Check the extcode* properties of the selfdestructing contract + current_index = next(storage_index) + entry_code += Op.SSTORE( + current_index, + Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), + ) + entry_code_storage[current_index] = len(selfdestruct_code) + + current_index = next(storage_index) + entry_code += Op.SSTORE( + current_index, + Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), + ) + # entry_code_storage[current_index] = bytes(keccak256(selfdestruct_code if eip_enabled else b"")) + # TODO: Don't really understand why this works. It should be empty if EIP is disabled, but it works if it's not + entry_code_storage[current_index] = bytes(keccak256(selfdestruct_code)) + + # Lastly return "0x00" so the entry point contract is created and we can retain the stored + # values for verification. + entry_code += Op.RETURN(0, 1) + + post: Dict[str, Account] = {} + if sendall_amount > 0: + post[to_address(sendall_destination_address)] = Account(balance=sendall_amount) + else: + post[to_address(sendall_destination_address)] = Account.NONEXISTENT # type: ignore if eip_enabled: - post[selfdestruct_contract_address] = Account(balance=0, code=selfdestruct_code) + post[selfdestruct_contract_address] = Account( + balance=0, code=selfdestruct_code, storage={0: call_times} + ) else: post[selfdestruct_contract_address] = Account.NONEXISTENT # type: ignore - txs: List[Transaction] = [] + post[entry_code_address] = Account( + code="0x00", + storage=entry_code_storage, + ) + nonce = count() - if prefunding_tx: - # Send some balance to the account to be created before it is created - txs += [ - Transaction( - ty=0x0, - chain_id=0x0, - value=selfdestruct_contract_initial_balance, - nonce=next(nonce), - to=selfdestruct_contract_address, - gas_limit=100000000, - gas_price=10, - protected=False, - ) - ] - txs += [ + txs = [ Transaction( ty=0x0, + value=0, data=selfdestruct_contract_initcode, chain_id=0x0, - value=0, nonce=next(nonce), to=None, - gas_limit=100000000, + gas_limit=100_000_000, gas_price=10, protected=False, ), Transaction( ty=0x0, + value=100_000, + data=entry_code, chain_id=0x0, - value=selfdestruct_contract_initial_balance if not prefunding_tx else 0, nonce=next(nonce), - to=selfdestruct_contract_address, - gas_limit=100000000, + to=None, + gas_limit=100_000_000, gas_price=10, protected=False, ), From 39c1042e8acc07c9d3157d1ffef904e71c8bc161 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Sun, 9 Jul 2023 23:11:19 +0000 Subject: [PATCH 20/38] tests/6780: better usage of storage --- .../eip6780_selfdestruct/test_selfdestruct.py | 106 +++++------------- 1 file changed, 31 insertions(+), 75 deletions(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index f02bce6d68..3a27e5ea1f 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -185,8 +185,7 @@ def test_create_selfdestruct_same_tx( ): # Our entry point is an initcode that in turn creates a self-destructing contract entry_code_address = compute_create_address(TestAddress, 0) - entry_code_storage: Storage.StorageDictType = {} - storage_index = count() + entry_code_storage = Storage() sendall_amount = 0 # Bytecode used to create the contract, can be CREATE or CREATE2 @@ -211,34 +210,27 @@ def test_create_selfdestruct_same_tx( ) # And we store the created address for verification purposes + Op.SSTORE( - 0, + entry_code_storage.store_next(selfdestruct_contract_address), create_bytecode, ) ) - # Store the created address - entry_code_storage[next(storage_index)] = selfdestruct_contract_address # Store the extcode* properties of the created address - current_index = next(storage_index) entry_code += Op.SSTORE( - current_index, + entry_code_storage.store_next(len(selfdestruct_code)), Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), ) - entry_code_storage[current_index] = len(selfdestruct_code) - current_index = next(storage_index) entry_code += Op.SSTORE( - current_index, + entry_code_storage.store_next(keccak256(selfdestruct_code)), Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), ) - entry_code_storage[current_index] = bytes(keccak256(selfdestruct_code)) # Call the self-destructing contract multiple times as required, incrementing the wei sent each # time for i in range(call_times): - current_index = next(storage_index) entry_code += Op.SSTORE( - current_index, + entry_code_storage.store_next(1), Op.CALL( Op.GASLIMIT, # Gas Op.PUSH20(selfdestruct_contract_address), # Address @@ -249,34 +241,25 @@ def test_create_selfdestruct_same_tx( 0, ), ) - entry_code_storage[current_index] = 1 # TODO: Why is this correct ?? sendall_amount += i - current_index = next(storage_index) entry_code += Op.SSTORE( - current_index, + entry_code_storage.store_next(0), Op.BALANCE(Op.PUSH20(selfdestruct_contract_address)), ) - entry_code_storage[current_index] = 0 # Check the extcode* properties of the selfdestructing contract again - current_index = next(storage_index) entry_code += Op.SSTORE( - current_index, + entry_code_storage.store_next(len(selfdestruct_code)), Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), ) - entry_code_storage[current_index] = len(selfdestruct_code) - current_index = next(storage_index) entry_code += Op.SSTORE( - current_index, + entry_code_storage.store_next(keccak256(selfdestruct_code if call_times > 0 else b"")), Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), ) - entry_code_storage[current_index] = bytes( - keccak256(selfdestruct_code if call_times > 0 else b"") - ) # Lastly return "0x00" so the entry point contract is created and we can retain the stored # values for verification. @@ -336,8 +319,7 @@ def test_selfdestructing_initcode( ): # Our entry point is an initcode that in turn creates a self-destructing contract entry_code_address = compute_create_address(TestAddress, 0) - entry_code_storage: Storage.StorageDictType = {} - storage_index = count() + entry_code_storage = Storage() sendall_amount = 0 # Bytecode used to create the contract, can be CREATE or CREATE2 @@ -362,34 +344,27 @@ def test_selfdestructing_initcode( ) # And we store the created address for verification purposes + Op.SSTORE( - 0, + entry_code_storage.store_next(selfdestruct_contract_address), create_bytecode, ) ) - # Store the created address - entry_code_storage[next(storage_index)] = selfdestruct_contract_address # Store the extcode* properties of the created address - current_index = next(storage_index) entry_code += Op.SSTORE( - current_index, + entry_code_storage.store_next(0), Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), ) - entry_code_storage[current_index] = 0 - current_index = next(storage_index) entry_code += Op.SSTORE( - current_index, + entry_code_storage.store_next(keccak256(bytes())), Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), ) - entry_code_storage[current_index] = bytes(keccak256(bytes())) # Call the self-destructing contract multiple times as required, incrementing the wei sent each # time for i in range(call_times): - current_index = next(storage_index) entry_code += Op.SSTORE( - current_index, + entry_code_storage.store_next(1), Op.CALL( Op.GASLIMIT, # Gas Op.PUSH20(selfdestruct_contract_address), # Address @@ -400,14 +375,11 @@ def test_selfdestructing_initcode( 0, ), ) - entry_code_storage[current_index] = 1 - current_index = next(storage_index) entry_code += Op.SSTORE( - current_index, + entry_code_storage.store_next(0), Op.BALANCE(Op.PUSH20(selfdestruct_contract_address)), ) - entry_code_storage[current_index] = 0 # Lastly return "0x00" so the entry point contract is created and we can retain the stored # values for verification. @@ -466,7 +438,7 @@ def test_recreate_selfdestructed_contract( 0x100 ) # Needs to be constant to be able to recreate the contract initcode_copy_from_address = to_address(0x200) - entry_code_storage: Storage.StorageDictType = {} + entry_code_storage = Storage() # Calculate the address of the selfdestructing contract if create_opcode == Op.CREATE: @@ -569,8 +541,7 @@ def test_selfdestruct_pre_existing( called, its balance is sent to the destination address. """ entry_code_address = compute_create_address(TestAddress, 0) - entry_code_storage: Storage.StorageDictType = {} - storage_index = count() + entry_code_storage = Storage() sendall_amount = selfdestruct_contract_initial_balance entry_code = b"" @@ -580,9 +551,8 @@ def test_selfdestruct_pre_existing( # Call the self-destructing contract multiple times as required, incrementing the wei sent each # time for i in range(call_times): - current_index = next(storage_index) entry_code += Op.SSTORE( - current_index, + entry_code_storage.store_next(1), Op.CALL( Op.GASLIMIT, # Gas Op.PUSH20(selfdestruct_contract_address), # Address @@ -593,33 +563,27 @@ def test_selfdestruct_pre_existing( 0, ), ) - entry_code_storage[current_index] = 1 sendall_amount += i - current_index = next(storage_index) entry_code += Op.SSTORE( - current_index, + entry_code_storage.store_next(0), Op.BALANCE(Op.PUSH20(selfdestruct_contract_address)), ) - entry_code_storage[current_index] = 0 # Check the extcode* properties of the selfdestructing contract - current_index = next(storage_index) entry_code += Op.SSTORE( - current_index, + entry_code_storage.store_next(len(selfdestruct_code)), Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), ) - entry_code_storage[current_index] = len(selfdestruct_code) - current_index = next(storage_index) entry_code += Op.SSTORE( - current_index, + # entry_code_storage.store_next(keccak256(selfdestruct_code if eip_enabled else b"")) + # TODO: Don't really understand why this works. It should be empty if EIP is disabled, + # but it works if it's not + entry_code_storage.store_next(keccak256(selfdestruct_code)), Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), ) - # entry_code_storage[current_index] = bytes(keccak256(selfdestruct_code if eip_enabled else b"")) - # TODO: Don't really understand why this works. It should be empty if EIP is disabled, but it works if it's not - entry_code_storage[current_index] = bytes(keccak256(selfdestruct_code)) # Lastly return "0x00" so the entry point contract is created and we can retain the stored # values for verification. @@ -681,8 +645,7 @@ def test_selfdestruct_created_same_block_different_tx( called, its balance is sent to the zero address. """ entry_code_address = compute_create_address(TestAddress, 1) - entry_code_storage: Storage.StorageDictType = {} - storage_index = count() + entry_code_storage = Storage() sendall_amount = selfdestruct_contract_initial_balance entry_code = b"" @@ -692,9 +655,8 @@ def test_selfdestruct_created_same_block_different_tx( # Call the self-destructing contract multiple times as required, incrementing the wei sent each # time for i in range(call_times): - current_index = next(storage_index) entry_code += Op.SSTORE( - current_index, + entry_code_storage.store_next(1), Op.CALL( Op.GASLIMIT, # Gas Op.PUSH20(selfdestruct_contract_address), # Address @@ -705,33 +667,27 @@ def test_selfdestruct_created_same_block_different_tx( 0, ), ) - entry_code_storage[current_index] = 1 sendall_amount += i - current_index = next(storage_index) entry_code += Op.SSTORE( - current_index, + entry_code_storage.store_next(0), Op.BALANCE(Op.PUSH20(selfdestruct_contract_address)), ) - entry_code_storage[current_index] = 0 # Check the extcode* properties of the selfdestructing contract - current_index = next(storage_index) entry_code += Op.SSTORE( - current_index, + entry_code_storage.store_next(len(selfdestruct_code)), Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), ) - entry_code_storage[current_index] = len(selfdestruct_code) - current_index = next(storage_index) entry_code += Op.SSTORE( - current_index, + # entry_code_storage.store_next(keccak256(selfdestruct_code if eip_enabled else b"")) + # TODO: Don't really understand why this works. It should be empty if EIP is disabled, + # but it works if it's not + entry_code_storage.store_next(keccak256(selfdestruct_code)), Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), ) - # entry_code_storage[current_index] = bytes(keccak256(selfdestruct_code if eip_enabled else b"")) - # TODO: Don't really understand why this works. It should be empty if EIP is disabled, but it works if it's not - entry_code_storage[current_index] = bytes(keccak256(selfdestruct_code)) # Lastly return "0x00" so the entry point contract is created and we can retain the stored # values for verification. From ba286c5401d14dd023697b55a3b03277c9ffc1d8 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Mon, 10 Jul 2023 01:09:17 +0000 Subject: [PATCH 21/38] tests/6780: check sendall recipient execution --- .../eip6780_selfdestruct/test_selfdestruct.py | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index 3a27e5ea1f..da354160bd 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -72,20 +72,20 @@ def env() -> Environment: @pytest.fixture -def sendall_destination_address() -> int: +def sendall_recipient_address() -> int: """Account that receives the balance from the self-destructed account.""" return 0x1234 @pytest.fixture def selfdestruct_code( - sendall_destination_address: int, + sendall_recipient_address: int, ) -> bytes: """Bytecode that self-destructs.""" return Op.SSTORE( 0, Op.ADD(Op.SLOAD(0), 1), # Add to the SSTORE'd value each time we enter the contract - ) + Op.SELFDESTRUCT(sendall_destination_address) + ) + Op.SELFDESTRUCT(sendall_recipient_address) @pytest.fixture @@ -143,6 +143,7 @@ def pre( selfdestruct_code: bytes, selfdestruct_contract_address: str, selfdestruct_contract_initial_balance: int, + sendall_recipient_address: int, ) -> Dict[str, Account]: """Pre-state of all tests""" pre = { @@ -162,6 +163,13 @@ def pre( balance=selfdestruct_contract_initial_balance, ) + # Send-all recipient account contains code that unconditionally resets an storage key upon + # entry, so we can check that it was not executed + pre[to_address(sendall_recipient_address)] = Account( + code=Op.SSTORE(0, 0), + storage={0: 1}, + ) + return pre @@ -177,7 +185,7 @@ def test_create_selfdestruct_same_tx( selfdestruct_code: bytes, selfdestruct_contract_initcode: bytes, selfdestruct_contract_address: str, - sendall_destination_address: int, + sendall_recipient_address: int, initcode_copy_from_address: str, create_opcode: Op, call_times: int, @@ -279,9 +287,8 @@ def test_create_selfdestruct_same_tx( initcode_copy_from_address: Account( code=selfdestruct_contract_initcode, ), + to_address(sendall_recipient_address): Account(balance=sendall_amount, storage={0: 1}), } - if sendall_amount > 0: - post[to_address(sendall_destination_address)] = Account(balance=sendall_amount) nonce = count() tx = Transaction( @@ -311,7 +318,7 @@ def test_selfdestructing_initcode( pre: Dict[str, Account], selfdestruct_contract_initcode: bytes, selfdestruct_contract_address: str, - sendall_destination_address: int, + sendall_recipient_address: int, initcode_copy_from_address: str, create_opcode: Op, call_times: int, # Number of times to call the self-destructing contract in the same tx @@ -399,9 +406,8 @@ def test_selfdestructing_initcode( initcode_copy_from_address: Account( code=selfdestruct_contract_initcode, ), + to_address(sendall_recipient_address): Account(balance=sendall_amount, storage={0: 1}), } - if sendall_amount > 0: - post[to_address(sendall_destination_address)] = Account(balance=sendall_amount) nonce = count() tx = Transaction( @@ -533,7 +539,7 @@ def test_selfdestruct_pre_existing( selfdestruct_contract_address: str, selfdestruct_code: bytes, selfdestruct_contract_initial_balance: int, - sendall_destination_address: int, + sendall_recipient_address: int, call_times: int, ): """ @@ -589,11 +595,13 @@ def test_selfdestruct_pre_existing( # values for verification. entry_code += Op.RETURN(0, 1) - post: Dict[str, Account] = {} - if sendall_amount > 0: - post[to_address(sendall_destination_address)] = Account(balance=sendall_amount) - else: - post[to_address(sendall_destination_address)] = Account.NONEXISTENT # type: ignore + post: Dict[str, Account] = { + entry_code_address: Account( + code="0x00", + storage=entry_code_storage, + ), + to_address(sendall_recipient_address): Account(balance=sendall_amount, storage={0: 1}), + } if eip_enabled: post[selfdestruct_contract_address] = Account( @@ -602,11 +610,6 @@ def test_selfdestruct_pre_existing( else: post[selfdestruct_contract_address] = Account.NONEXISTENT # type: ignore - post[entry_code_address] = Account( - code="0x00", - storage=entry_code_storage, - ) - nonce = count() tx = Transaction( ty=0x0, @@ -637,7 +640,7 @@ def test_selfdestruct_created_same_block_different_tx( selfdestruct_code: bytes, selfdestruct_contract_initcode: bytes, selfdestruct_contract_initial_balance: int, - sendall_destination_address: int, + sendall_recipient_address: int, call_times: int, ): """ @@ -693,11 +696,13 @@ def test_selfdestruct_created_same_block_different_tx( # values for verification. entry_code += Op.RETURN(0, 1) - post: Dict[str, Account] = {} - if sendall_amount > 0: - post[to_address(sendall_destination_address)] = Account(balance=sendall_amount) - else: - post[to_address(sendall_destination_address)] = Account.NONEXISTENT # type: ignore + post: Dict[str, Account] = { + entry_code_address: Account( + code="0x00", + storage=entry_code_storage, + ), + to_address(sendall_recipient_address): Account(balance=sendall_amount, storage={0: 1}), + } if eip_enabled: post[selfdestruct_contract_address] = Account( @@ -706,11 +711,6 @@ def test_selfdestruct_created_same_block_different_tx( else: post[selfdestruct_contract_address] = Account.NONEXISTENT # type: ignore - post[entry_code_address] = Account( - code="0x00", - storage=entry_code_storage, - ) - nonce = count() txs = [ Transaction( From 564bbd06693fcf0072c4b7dff035580a4391273f Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Mon, 10 Jul 2023 21:18:19 +0000 Subject: [PATCH 22/38] tests/6780: add self-destructing create tx initcode --- .../eip6780_selfdestruct/test_selfdestruct.py | 70 +++++++++++++++---- 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index da354160bd..564ddef6f4 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -48,10 +48,6 @@ # - Test selfdestructing using all types of create # - Create a contract using CREATE2, then in a subsequent tx do CREATE2 to the same address, and # try to self-destruct -# - Create a contract using CREATE2 to an account that has some balance, and try to self-destruct -# it in the same tx -# - Check balance after self-destruct using Op.BALANCE -# - Check that the Op.SENDALL recipient does not execute code # - Delegate call to a contract that contains self-destruct and was created in the current tx # from a contract that was created in a previous tx @@ -182,6 +178,7 @@ def test_create_selfdestruct_same_tx( state_test: StateTestFiller, env: Environment, pre: Dict[str, Account], + entry_code_address: str, selfdestruct_code: bytes, selfdestruct_contract_initcode: bytes, selfdestruct_contract_address: str, @@ -192,7 +189,6 @@ def test_create_selfdestruct_same_tx( selfdestruct_contract_initial_balance: int, ): # Our entry point is an initcode that in turn creates a self-destructing contract - entry_code_address = compute_create_address(TestAddress, 0) entry_code_storage = Storage() sendall_amount = 0 @@ -316,6 +312,7 @@ def test_selfdestructing_initcode( state_test: StateTestFiller, env: Environment, pre: Dict[str, Account], + entry_code_address: str, selfdestruct_contract_initcode: bytes, selfdestruct_contract_address: str, sendall_recipient_address: int, @@ -325,7 +322,6 @@ def test_selfdestructing_initcode( selfdestruct_contract_initial_balance: int, ): # Our entry point is an initcode that in turn creates a self-destructing contract - entry_code_address = compute_create_address(TestAddress, 0) entry_code_storage = Storage() sendall_amount = 0 @@ -425,6 +421,53 @@ def test_selfdestructing_initcode( state_test(env=env, pre=pre, post=post, txs=[tx]) +@pytest.mark.parametrize("tx_value", [0, 100_000]) +@pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 100_000]) +@pytest.mark.parametrize("selfdestructing_initcode", [True], ids=[""]) +@pytest.mark.parametrize("selfdestruct_contract_address", [compute_create_address(TestAddress, 0)]) +@pytest.mark.parametrize("eip_enabled", [True, False]) +@pytest.mark.valid_from("Shanghai") +def test_selfdestructing_initcode_create_tx( + state_test: StateTestFiller, + env: Environment, + pre: Dict[str, Account], + tx_value: int, + entry_code_address: str, + selfdestruct_contract_initcode: bytes, + selfdestruct_contract_address: str, + sendall_recipient_address: int, + initcode_copy_from_address: str, + selfdestruct_contract_initial_balance: int, +): + assert entry_code_address == selfdestruct_contract_address + + # Our entry point is an initcode that in turn creates a self-destructing contract + sendall_amount = selfdestruct_contract_initial_balance + tx_value + + post: Dict[str, Account] = { + selfdestruct_contract_address: Account.NONEXISTENT, # type: ignore + initcode_copy_from_address: Account( + code=selfdestruct_contract_initcode, + ), + to_address(sendall_recipient_address): Account(balance=sendall_amount, storage={0: 1}), + } + + nonce = count() + tx = Transaction( + ty=0x0, + value=tx_value, + data=selfdestruct_contract_initcode, + chain_id=0x0, + nonce=next(nonce), + to=None, + gas_limit=100_000_000, + gas_price=10, + protected=False, + ) + + state_test(env=env, pre=pre, post=post, txs=[tx]) + + @pytest.mark.parametrize("create_opcode", [Op.CREATE2]) # Can only recreate using CREATE2 @pytest.mark.parametrize("recreate_times", [1]) @pytest.mark.parametrize("call_times", [1]) @@ -433,6 +476,7 @@ def test_selfdestructing_initcode( def test_recreate_selfdestructed_contract( blockchain_test: BlockchainTestFiller, env: Environment, + entry_code_address: str, selfdestruct_contract_initcode: bytes, create_opcode: Op, recreate_times: int, # Number of times to recreate the contract in different transactions @@ -440,9 +484,6 @@ def test_recreate_selfdestructed_contract( ): assert create_opcode == Op.CREATE2, "cannot recreate contract using CREATE opcode" - entry_code_address = to_address( - 0x100 - ) # Needs to be constant to be able to recreate the contract initcode_copy_from_address = to_address(0x200) entry_code_storage = Storage() @@ -507,7 +548,7 @@ def test_recreate_selfdestructed_contract( data=to_hash_bytes(i), chain_id=0x0, nonce=next(nonce), - to=entry_code_address, + to=entry_code_address if i > 0 else None, # First call creates the contract gas_limit=100_000_000, gas_price=10, protected=False, @@ -536,6 +577,7 @@ def test_selfdestruct_pre_existing( eip_enabled: bool, env: Environment, pre: Dict[str, Account], + entry_code_address: str, selfdestruct_contract_address: str, selfdestruct_code: bytes, selfdestruct_contract_initial_balance: int, @@ -546,7 +588,6 @@ def test_selfdestruct_pre_existing( Test that if a previously created account that contains a selfdestruct is called, its balance is sent to the destination address. """ - entry_code_address = compute_create_address(TestAddress, 0) entry_code_storage = Storage() sendall_amount = selfdestruct_contract_initial_balance entry_code = b"" @@ -628,7 +669,10 @@ def test_selfdestruct_pre_existing( @pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 1]) @pytest.mark.parametrize("call_times", [1, 10]) -@pytest.mark.parametrize("selfdestruct_contract_address", [compute_create_address(TestAddress, 0)]) +@pytest.mark.parametrize( + "selfdestruct_contract_address,entry_code_address", + [(compute_create_address(TestAddress, 0), compute_create_address(TestAddress, 1))], +) @pytest.mark.parametrize("eip_enabled", [True, False]) @pytest.mark.valid_from("Shanghai") def test_selfdestruct_created_same_block_different_tx( @@ -636,6 +680,7 @@ def test_selfdestruct_created_same_block_different_tx( eip_enabled: bool, env: Environment, pre: Dict[str, Account], + entry_code_address: str, selfdestruct_contract_address: str, selfdestruct_code: bytes, selfdestruct_contract_initcode: bytes, @@ -647,7 +692,6 @@ def test_selfdestruct_created_same_block_different_tx( Test that if an account created in the same block that contains a selfdestruct is called, its balance is sent to the zero address. """ - entry_code_address = compute_create_address(TestAddress, 1) entry_code_storage = Storage() sendall_amount = selfdestruct_contract_initial_balance entry_code = b"" From 2b23bff1b40ed106c13193eb14c4f18b2aa67e71 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Mon, 10 Jul 2023 23:02:52 +0000 Subject: [PATCH 23/38] tests/6780: add delegate call tests --- .../eip6780_selfdestruct/test_selfdestruct.py | 329 +++++++++++++++++- 1 file changed, 324 insertions(+), 5 deletions(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index 564ddef6f4..36e7e9686e 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -73,17 +73,27 @@ def sendall_recipient_address() -> int: return 0x1234 -@pytest.fixture -def selfdestruct_code( +def selfdestruct_code_preset( sendall_recipient_address: int, ) -> bytes: - """Bytecode that self-destructs.""" + """Return a bytecode that self-destructs.""" return Op.SSTORE( 0, Op.ADD(Op.SLOAD(0), 1), # Add to the SSTORE'd value each time we enter the contract ) + Op.SELFDESTRUCT(sendall_recipient_address) +@pytest.fixture +def selfdestruct_code( + sendall_recipient_address: int, +) -> bytes: + """ + Creates the default self-destructing bytecode, + which can be modified by each test if necessary. + """ + return selfdestruct_code_preset(sendall_recipient_address) + + @pytest.fixture def selfdestructing_initcode() -> bool: """ @@ -136,7 +146,6 @@ def selfdestruct_contract_address( def pre( initcode_copy_from_address: str, selfdestruct_contract_initcode: bytes, - selfdestruct_code: bytes, selfdestruct_contract_address: str, selfdestruct_contract_initial_balance: int, sendall_recipient_address: int, @@ -155,7 +164,7 @@ def pre( # Also put a pre-existing copy of the self-destruct contract in a known place pre[PRE_EXISTING_SELFDESTRUCT_ADDRESS] = Account( - code=selfdestruct_code, + code=selfdestruct_code_preset(sendall_recipient_address), balance=selfdestruct_contract_initial_balance, ) @@ -782,3 +791,313 @@ def test_selfdestruct_created_same_block_different_tx( ] blockchain_test(genesis_environment=env, pre=pre, post=post, blocks=[Block(txs=txs)]) + + +@pytest.mark.parametrize( + "selfdestruct_code", + [ + Op.DELEGATECALL( + Op.GAS, + Op.PUSH20(PRE_EXISTING_SELFDESTRUCT_ADDRESS), + 0, + 0, + 0, + 0, + ), + Op.CALLCODE( + Op.GAS, + Op.PUSH20(PRE_EXISTING_SELFDESTRUCT_ADDRESS), + 0, + 0, + 0, + 0, + 0, + ), + ], + ids=["delegatecall", "callcode"], +) # The self-destruct code is delegatecall +@pytest.mark.parametrize("call_times", [1]) +@pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 1]) +@pytest.mark.parametrize("create_opcode", [Op.CREATE]) +@pytest.mark.parametrize("eip_enabled", [True, False]) +@pytest.mark.valid_from("Shanghai") +def test_delegatecall_from_new_contract_to_pre_existing_contract( + state_test: StateTestFiller, + env: Environment, + pre: Dict[str, Account], + entry_code_address: str, + selfdestruct_code: bytes, + selfdestruct_contract_initcode: bytes, + selfdestruct_contract_address: str, + sendall_recipient_address: int, + initcode_copy_from_address: str, + create_opcode: Op, + call_times: int, + selfdestruct_contract_initial_balance: int, +): + # Our entry point is an initcode that in turn creates a self-destructing contract + entry_code_storage = Storage() + sendall_amount = 0 + + # Bytecode used to create the contract, can be CREATE or CREATE2 + create_args = [ + 0, # Value + 0, # Offset + len(selfdestruct_contract_initcode), # Length + ] + if create_opcode == Op.CREATE2: + # CREATE2 requires a salt argument + create_args.append(0) + create_bytecode = create_opcode(*create_args) + + # 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( + Op.PUSH20(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(Op.PUSH20(selfdestruct_contract_address)), + ) + + entry_code += Op.SSTORE( + entry_code_storage.store_next(keccak256(selfdestruct_code)), + Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), + ) + + # Call the self-destructing contract multiple times as required, incrementing the wei sent each + # time + for i in range(call_times): + entry_code += Op.SSTORE( + entry_code_storage.store_next(1), + Op.CALL( + Op.GASLIMIT, # Gas + Op.PUSH20(selfdestruct_contract_address), # Address + i, # Value + 0, + 0, + 0, + 0, + ), + ) + + # TODO: Why is this correct ?? + sendall_amount += i + + entry_code += Op.SSTORE( + entry_code_storage.store_next(0), + Op.BALANCE(Op.PUSH20(selfdestruct_contract_address)), + ) + + # Check the extcode* properties of the selfdestructing contract again + entry_code += Op.SSTORE( + entry_code_storage.store_next(len(selfdestruct_code)), + Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), + ) + + entry_code += Op.SSTORE( + entry_code_storage.store_next(keccak256(selfdestruct_code)), + Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), + ) + + # Lastly return "0x00" so the entry point contract is created and we can retain the stored + # values for verification. + entry_code += Op.RETURN(len(selfdestruct_contract_initcode), 1) + + if selfdestruct_contract_initial_balance > 0: + # Address where the contract is created already had some balance, + # which must be included in the send-all operation + sendall_amount += selfdestruct_contract_initial_balance + + post: Dict[str, Account] = { + entry_code_address: Account( + code="0x00", + storage=entry_code_storage, + ), + selfdestruct_contract_address: Account.NONEXISTENT, # type: ignore + initcode_copy_from_address: Account( + code=selfdestruct_contract_initcode, + ), + to_address(sendall_recipient_address): Account(balance=sendall_amount, storage={0: 1}), + } + + nonce = count() + tx = Transaction( + ty=0x0, + value=100_000, + data=entry_code, + chain_id=0x0, + nonce=next(nonce), + to=None, + gas_limit=100_000_000, + gas_price=10, + protected=False, + ) + + state_test(env=env, pre=pre, post=post, txs=[tx]) + + +@pytest.mark.parametrize("create_opcode", [Op.CREATE, Op.CREATE2]) +@pytest.mark.parametrize("call_opcode", [Op.DELEGATECALL, Op.CALLCODE]) +@pytest.mark.parametrize("call_times", [1]) +@pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 1]) +@pytest.mark.parametrize("eip_enabled", [True, False]) +@pytest.mark.valid_from("Shanghai") +def test_delegatecall_from_pre_existing_contract_to_new_contract( + state_test: StateTestFiller, + eip_enabled: bool, + env: Environment, + pre: Dict[str, Account], + entry_code_address: str, + selfdestruct_code: bytes, + selfdestruct_contract_initcode: bytes, + selfdestruct_contract_address: str, + sendall_recipient_address: int, + initcode_copy_from_address: str, + call_opcode: Op, + create_opcode: Op, + call_times: int, + selfdestruct_contract_initial_balance: int, +): + # Add the contract that delegate calls to the newly created contract + delegate_caller_address = "0x2222222222222222222222222222222222222222" + call_args: List[int | bytes] = [ + Op.GAS(), + Op.PUSH20(selfdestruct_contract_address), + 0, + 0, + 0, + 0, + ] + if call_opcode == Op.CALLCODE: + # CALLCODE requires `value` + call_args.append(0) + delegate_caller_code = call_opcode(*call_args) + pre[delegate_caller_address] = Account(code=delegate_caller_code) + + # Our entry point is an initcode that in turn creates a self-destructing contract + entry_code_storage = Storage() + sendall_amount = 0 + + # Bytecode used to create the contract, can be CREATE or CREATE2 + create_args = [ + 0, # Value + 0, # Offset + len(selfdestruct_contract_initcode), # Length + ] + if create_opcode == Op.CREATE2: + # CREATE2 requires a salt argument + create_args.append(0) + create_bytecode = create_opcode(*create_args) + + # 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( + Op.PUSH20(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 pre-existing address + entry_code += Op.SSTORE( + entry_code_storage.store_next(len(delegate_caller_code)), + Op.EXTCODESIZE(Op.PUSH20(delegate_caller_address)), + ) + + entry_code += Op.SSTORE( + entry_code_storage.store_next(keccak256(delegate_caller_code)), + Op.EXTCODEHASH(Op.PUSH20(delegate_caller_address)), + ) + + # Now instead of calling the newly created contract directly, we delegate call to it + # from a pre-existing contract, and the contract must not self-destruct + for i in range(call_times): + entry_code += Op.SSTORE( + entry_code_storage.store_next(1), + Op.CALL( + Op.GASLIMIT, # Gas + Op.PUSH20(delegate_caller_address), # Address + i, # Value + 0, + 0, + 0, + 0, + ), + ) + + # TODO: Why is this correct ?? + sendall_amount += i + + entry_code += Op.SSTORE( + entry_code_storage.store_next(0), + Op.BALANCE(Op.PUSH20(delegate_caller_address)), + ) + + # Check the extcode* properties of the pre-existing address again + entry_code += Op.SSTORE( + entry_code_storage.store_next(len(selfdestruct_code)), + Op.EXTCODESIZE(Op.PUSH20(delegate_caller_address)), + ) + + entry_code += Op.SSTORE( + entry_code_storage.store_next(keccak256(selfdestruct_code)), + Op.EXTCODEHASH(Op.PUSH20(delegate_caller_address)), + ) + + # Lastly return "0x00" so the entry point contract is created and we can retain the stored + # values for verification. + entry_code += Op.RETURN(len(selfdestruct_contract_initcode), 1) + + post: Dict[str, Account] = { + entry_code_address: Account( + code="0x00", + storage=entry_code_storage, + ), + selfdestruct_contract_address: Account( + code=selfdestruct_code, balance=selfdestruct_contract_initial_balance + ), + initcode_copy_from_address: Account( + code=selfdestruct_contract_initcode, + ), + to_address(sendall_recipient_address): Account(balance=sendall_amount, storage={0: 1}), + } + + if eip_enabled: + post[delegate_caller_address] = Account(code=delegate_caller_code, balance=0) + else: + post[delegate_caller_address] = Account.NONEXISTENT # type: ignore + + nonce = count() + tx = Transaction( + ty=0x0, + value=100_000, + data=entry_code, + chain_id=0x0, + nonce=next(nonce), + to=None, + gas_limit=100_000_000, + gas_price=10, + protected=False, + ) + + state_test(env=env, pre=pre, post=post, txs=[tx]) From deb6a961bdec6b74773fba56fea1c6f3a253e502 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Mon, 10 Jul 2023 23:03:10 +0000 Subject: [PATCH 24/38] tests/6780: refactor recreate test --- .../eip6780_selfdestruct/test_selfdestruct.py | 73 ++++++++----------- 1 file changed, 32 insertions(+), 41 deletions(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index 36e7e9686e..edf9e36b82 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -24,7 +24,6 @@ compute_create2_address, compute_create_address, to_address, - to_hash_bytes, ) from ethereum_test_tools.vm.opcode import Opcodes as Op @@ -202,15 +201,15 @@ def test_create_selfdestruct_same_tx( sendall_amount = 0 # Bytecode used to create the contract, can be CREATE or CREATE2 - op_args = [ + create_args = [ 0, # Value 0, # Offset len(selfdestruct_contract_initcode), # Length ] if create_opcode == Op.CREATE2: # CREATE2 requires a salt argument - op_args.append(0) - create_bytecode = create_opcode(*op_args) + create_args.append(0) + create_bytecode = create_opcode(*create_args) # Entry code that will be executed, creates the contract and then calls it in the same tx entry_code = ( @@ -270,7 +269,7 @@ def test_create_selfdestruct_same_tx( ) entry_code += Op.SSTORE( - entry_code_storage.store_next(keccak256(selfdestruct_code if call_times > 0 else b"")), + entry_code_storage.store_next(keccak256(selfdestruct_code)), Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), ) @@ -335,15 +334,15 @@ def test_selfdestructing_initcode( sendall_amount = 0 # Bytecode used to create the contract, can be CREATE or CREATE2 - op_args = [ + create_args = [ 0, # Value 0, # Offset len(selfdestruct_contract_initcode), # Length ] if create_opcode == Op.CREATE2: # CREATE2 requires a salt argument - op_args.append(0) - create_bytecode = create_opcode(*op_args) + create_args.append(0) + create_bytecode = create_opcode(*create_args) # Entry code that will be executed, creates the contract and then calls it in the same tx entry_code = ( @@ -478,40 +477,34 @@ def test_selfdestructing_initcode_create_tx( @pytest.mark.parametrize("create_opcode", [Op.CREATE2]) # Can only recreate using CREATE2 +@pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 100_000]) @pytest.mark.parametrize("recreate_times", [1]) @pytest.mark.parametrize("call_times", [1]) @pytest.mark.parametrize("eip_enabled", [True, False]) @pytest.mark.valid_from("Shanghai") -def test_recreate_selfdestructed_contract( +def test_recreate_selfdestructed_contract_different_txs( blockchain_test: BlockchainTestFiller, env: Environment, + pre: Dict[str, Account], entry_code_address: str, selfdestruct_contract_initcode: bytes, + selfdestruct_contract_address: str, + selfdestruct_contract_initial_balance: int, + sendall_recipient_address: int, + initcode_copy_from_address: str, create_opcode: Op, recreate_times: int, # Number of times to recreate the contract in different transactions call_times: int, # Number of times to call the self-destructing contract in the same tx ): - assert create_opcode == Op.CREATE2, "cannot recreate contract using CREATE opcode" - - initcode_copy_from_address = to_address(0x200) + """ + Test that a contract can be recreated after it has self-destructed. + """ entry_code_storage = Storage() - - # Calculate the address of the selfdestructing contract - if create_opcode == Op.CREATE: - selfdestruct_contract_address = compute_create_address(entry_code_address, 1) - elif create_opcode == Op.CREATE2: - selfdestruct_contract_address = compute_create2_address( - entry_code_address, 0, selfdestruct_contract_initcode - ) - else: - raise Exception("Invalid opcode") + sendall_amount = selfdestruct_contract_initial_balance # Bytecode used to create the contract - op_args = [0, 0, len(selfdestruct_contract_initcode)] - if create_opcode == Op.CREATE2: - op_args.append(0) - - create_bytecode = create_opcode(*op_args) + assert create_opcode == Op.CREATE2, "cannot recreate contract using CREATE opcode" + create_bytecode = Op.CREATE2(0, 0, len(selfdestruct_contract_initcode), 0) # Entry code that will be executed, creates the contract and then calls it entry_code = ( @@ -523,7 +516,7 @@ def test_recreate_selfdestructed_contract( len(selfdestruct_contract_initcode), ) + Op.SSTORE( - Op.CALLDATALOAD(0), + Op.ADD(Op.SLOAD(0), 1), create_bytecode, ) ) @@ -538,23 +531,18 @@ def test_recreate_selfdestructed_contract( 0, 0, ) - # CHECK BALANCE HERE + sendall_amount += i entry_code += Op.RETURN(len(selfdestruct_contract_initcode), 1) - pre = { - TestAddress: Account(balance=10_000_000_00000000000000), - entry_code_address: Account(code=entry_code), - initcode_copy_from_address: Account(code=selfdestruct_contract_initcode), - } - txs: List[Transaction] = [] nonce = count() for i in range(recreate_times + 1): txs.append( Transaction( ty=0x0, - data=to_hash_bytes(i), + # The number of the call is sent as parameter, and this is also the storage key + data=entry_code, chain_id=0x0, nonce=next(nonce), to=entry_code_address if i > 0 else None, # First call creates the contract @@ -563,14 +551,17 @@ def test_recreate_selfdestructed_contract( protected=False, ) ) - entry_code_storage[i] = selfdestruct_contract_address - - entry_address_expected_code = entry_code post: Dict[str, Account] = { - entry_code_address: Account(code=entry_address_expected_code, storage=entry_code_storage), + entry_code_address: Account( + code="0x00", + storage=entry_code_storage, + ), selfdestruct_contract_address: Account.NONEXISTENT, # type: ignore - initcode_copy_from_address: Account(code=selfdestruct_contract_initcode), + initcode_copy_from_address: Account( + code=selfdestruct_contract_initcode, + ), + to_address(sendall_recipient_address): Account(balance=sendall_amount, storage={0: 1}), } blockchain_test(genesis_environment=env, pre=pre, post=post, blocks=[Block(txs=txs)]) From 526914f46f3a9cadde16b40bf13496510a358368 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Mon, 10 Jul 2023 23:39:21 +0000 Subject: [PATCH 25/38] tests/6780: nit --- tests/cancun/eip6780_selfdestruct/test_selfdestruct.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index edf9e36b82..4df7e16835 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -541,7 +541,6 @@ def test_recreate_selfdestructed_contract_different_txs( txs.append( Transaction( ty=0x0, - # The number of the call is sent as parameter, and this is also the storage key data=entry_code, chain_id=0x0, nonce=next(nonce), From 4464258bc7008f54914217ef99387a62f22c36d5 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Tue, 11 Jul 2023 18:49:04 +0000 Subject: [PATCH 26/38] tests/6780: add post-selfdestruct code check --- .../eip6780_selfdestruct/test_selfdestruct.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index 4df7e16835..e59e57bde6 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -36,7 +36,6 @@ # TODO: -# - Destroy and re-create multiple times in the same tx # - Create and destroy multiple contracts in the same tx # - Create multiple contracts in a tx and destroy only one of them, but attempt to destroy the # other one in a subsequent tx @@ -49,6 +48,9 @@ # try to self-destruct # - Delegate call to a contract that contains self-destruct and was created in the current tx # from a contract that was created in a previous tx +# - SENDALL to multiple different contracts in a single tx, from a single or multiple contracts, +# all of which would not self destruct (or perhaps some of them would and some others won't) +# Recursive contract creation and self-destruction @pytest.fixture @@ -76,10 +78,15 @@ def selfdestruct_code_preset( sendall_recipient_address: int, ) -> bytes: """Return a bytecode that self-destructs.""" - return Op.SSTORE( - 0, - Op.ADD(Op.SLOAD(0), 1), # Add to the SSTORE'd value each time we enter the contract - ) + Op.SELFDESTRUCT(sendall_recipient_address) + return ( + Op.SSTORE( + 0, + Op.ADD(Op.SLOAD(0), 1), # Add to the SSTORE'd value each time we enter the contract + ) + + Op.SELFDESTRUCT(sendall_recipient_address) + # This should never be reached, even when the contract is not self-destructed + + Op.SSTORE(0, 0) + ) @pytest.fixture From ba84d67bd0b4fc709d03c3c29b2e4d74b436ac57 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Tue, 11 Jul 2023 19:02:57 +0000 Subject: [PATCH 27/38] tests/6780: selfdestruct code can sendall to calldata address --- .../eip6780_selfdestruct/test_selfdestruct.py | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index e59e57bde6..ce8460a49a 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -75,19 +75,29 @@ def sendall_recipient_address() -> int: def selfdestruct_code_preset( + *, sendall_recipient_address: int, + recipient_from_calldata: bool, ) -> bytes: """Return a bytecode that self-destructs.""" - return ( - Op.SSTORE( - 0, - Op.ADD(Op.SLOAD(0), 1), # Add to the SSTORE'd value each time we enter the contract - ) - + Op.SELFDESTRUCT(sendall_recipient_address) - # This should never be reached, even when the contract is not self-destructed - + Op.SSTORE(0, 0) + + # First save into store the count of how many times we've re-entered the contract + bytecode = Op.SSTORE( + 0, + Op.ADD(Op.SLOAD(0), 1), # Add to the SSTORE'd value each time we enter the contract ) + # Do the self-destruct, either using the recipient address from calldata or a preset one + if recipient_from_calldata: + bytecode += Op.SELFDESTRUCT(Op.CALLDATALOAD(0)) + else: + bytecode += Op.SELFDESTRUCT(sendall_recipient_address) + + # This should never be reached, even when the contract is not self-destructed + bytecode += Op.SSTORE(0, 0) + + return bytecode + @pytest.fixture def selfdestruct_code( @@ -97,7 +107,9 @@ def selfdestruct_code( Creates the default self-destructing bytecode, which can be modified by each test if necessary. """ - return selfdestruct_code_preset(sendall_recipient_address) + return selfdestruct_code_preset( + sendall_recipient_address=sendall_recipient_address, recipient_from_calldata=False + ) @pytest.fixture @@ -170,7 +182,10 @@ def pre( # Also put a pre-existing copy of the self-destruct contract in a known place pre[PRE_EXISTING_SELFDESTRUCT_ADDRESS] = Account( - code=selfdestruct_code_preset(sendall_recipient_address), + code=selfdestruct_code_preset( + sendall_recipient_address=sendall_recipient_address, + recipient_from_calldata=False, + ), balance=selfdestruct_contract_initial_balance, ) From aab61ebd78b09b49888b09f0141039be81485579 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Tue, 11 Jul 2023 20:28:57 +0000 Subject: [PATCH 28/38] tests/6780: sendall to multiple addresses in same tx --- .../eip6780_selfdestruct/test_selfdestruct.py | 76 +++++++++++++------ 1 file changed, 54 insertions(+), 22 deletions(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index ce8460a49a..7c67372f84 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -5,8 +5,8 @@ """ # noqa: E501 -from itertools import count -from typing import Dict, List +from itertools import count, cycle +from typing import Dict, List, Optional import pytest from ethereum.crypto.hash import keccak256 @@ -74,10 +74,17 @@ def sendall_recipient_address() -> int: return 0x1234 +@pytest.fixture +def sendall_recipient_addresses( + sendall_recipient_address: int, +) -> List[int]: + """List of possible addresses that can receive a SENDALL operation.""" + return [sendall_recipient_address] + + def selfdestruct_code_preset( *, - sendall_recipient_address: int, - recipient_from_calldata: bool, + sendall_recipient_address: Optional[int], ) -> bytes: """Return a bytecode that self-destructs.""" @@ -88,7 +95,7 @@ def selfdestruct_code_preset( ) # Do the self-destruct, either using the recipient address from calldata or a preset one - if recipient_from_calldata: + if sendall_recipient_address is None: bytecode += Op.SELFDESTRUCT(Op.CALLDATALOAD(0)) else: bytecode += Op.SELFDESTRUCT(sendall_recipient_address) @@ -101,14 +108,14 @@ def selfdestruct_code_preset( @pytest.fixture def selfdestruct_code( - sendall_recipient_address: int, + sendall_recipient_address: Optional[int], ) -> bytes: """ Creates the default self-destructing bytecode, which can be modified by each test if necessary. """ return selfdestruct_code_preset( - sendall_recipient_address=sendall_recipient_address, recipient_from_calldata=False + sendall_recipient_address=sendall_recipient_address, ) @@ -166,7 +173,8 @@ def pre( selfdestruct_contract_initcode: bytes, selfdestruct_contract_address: str, selfdestruct_contract_initial_balance: int, - sendall_recipient_address: int, + sendall_recipient_address: Optional[int], + sendall_recipient_addresses: List[int], ) -> Dict[str, Account]: """Pre-state of all tests""" pre = { @@ -184,17 +192,18 @@ def pre( pre[PRE_EXISTING_SELFDESTRUCT_ADDRESS] = Account( code=selfdestruct_code_preset( sendall_recipient_address=sendall_recipient_address, - recipient_from_calldata=False, ), balance=selfdestruct_contract_initial_balance, ) - # Send-all recipient account contains code that unconditionally resets an storage key upon + # Send-all recipient accounts contain code that unconditionally resets an storage key upon # entry, so we can check that it was not executed - pre[to_address(sendall_recipient_address)] = Account( - code=Op.SSTORE(0, 0), - storage={0: 1}, - ) + for sendall_recipient_address in sendall_recipient_addresses: + assert sendall_recipient_address is not None, "None send-all recipient address" + pre[to_address(sendall_recipient_address)] = Account( + code=Op.SSTORE(0, 0), + storage={0: 1}, + ) return pre @@ -589,7 +598,20 @@ def test_recreate_selfdestructed_contract_different_txs( @pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 1]) -@pytest.mark.parametrize("call_times", [1, 10]) +@pytest.mark.parametrize( + "call_times,sendall_recipient_addresses", + [ + (1, [0x1000]), + (10, [0x1000]), + (10, [0x1000, 0x2000, 0x3000]), + ], + ids=[ + "single_call", + "multiple_calls_single_sendall_recipient", + "multiple_calls_multiple_sendall_recipients", + ], +) +@pytest.mark.parametrize("sendall_recipient_address", [None]) # Calldata sendall recipient @pytest.mark.parametrize("selfdestruct_contract_address", [PRE_EXISTING_SELFDESTRUCT_ADDRESS]) @pytest.mark.parametrize("eip_enabled", [True, False]) @pytest.mark.valid_from("Shanghai") @@ -602,7 +624,7 @@ def test_selfdestruct_pre_existing( selfdestruct_contract_address: str, selfdestruct_code: bytes, selfdestruct_contract_initial_balance: int, - sendall_recipient_address: int, + sendall_recipient_addresses: List[int], call_times: int, ): """ @@ -610,15 +632,22 @@ def test_selfdestruct_pre_existing( called, its balance is sent to the destination address. """ entry_code_storage = Storage() - sendall_amount = selfdestruct_contract_initial_balance - entry_code = b"" + + # Final balances of the sendall recipients + sendall_final_balances = dict( + zip(sendall_recipient_addresses, [0] * len(sendall_recipient_addresses)) + ) + # Selfdestruct contract initial balance will be sent to the first sendall recipient + sendall_final_balances[sendall_recipient_addresses[0]] = selfdestruct_contract_initial_balance # Entry code in this case will simply call the pre-existing selfdestructing contract, # as many times as required + entry_code = b"" # Call the self-destructing contract multiple times as required, incrementing the wei sent each # time - for i in range(call_times): + for i, sendall_recipient in zip(range(call_times), cycle(sendall_recipient_addresses)): + entry_code += Op.MSTORE(0, Op.PUSH20(sendall_recipient)) entry_code += Op.SSTORE( entry_code_storage.store_next(1), Op.CALL( @@ -626,13 +655,13 @@ def test_selfdestruct_pre_existing( Op.PUSH20(selfdestruct_contract_address), # Address i, # Value 0, - 0, + 32, 0, 0, ), ) - sendall_amount += i + sendall_final_balances[sendall_recipient] += i entry_code += Op.SSTORE( entry_code_storage.store_next(0), @@ -662,9 +691,12 @@ def test_selfdestruct_pre_existing( code="0x00", storage=entry_code_storage, ), - to_address(sendall_recipient_address): Account(balance=sendall_amount, storage={0: 1}), } + # Check the balances of the sendall recipients + for address, balance in sendall_final_balances.items(): + post[to_address(address)] = Account(balance=balance, storage={0: 1}) + if eip_enabled: post[selfdestruct_contract_address] = Account( balance=0, code=selfdestruct_code, storage={0: call_times} From f116b9d433ef8d9bebbc6b912434783e77d7f255 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Tue, 11 Jul 2023 23:11:25 +0000 Subject: [PATCH 29/38] tests/6780: Add sendall to self tests --- .../eip6780_selfdestruct/test_selfdestruct.py | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index 7c67372f84..59fc672f09 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -39,15 +39,9 @@ # - Create and destroy multiple contracts in the same tx # - Create multiple contracts in a tx and destroy only one of them, but attempt to destroy the # other one in a subsequent tx -# - Create contract, attempt to destroy it in a subsequent tx in the same block # - Create a contract and try to destroy using another calltype (e.g. Delegatecall then destroy) # - Create a contract that creates another contract, then selfdestruct only the parent # (or vice versa) -# - Test selfdestructing using all types of create -# - Create a contract using CREATE2, then in a subsequent tx do CREATE2 to the same address, and -# try to self-destruct -# - Delegate call to a contract that contains self-destruct and was created in the current tx -# from a contract that was created in a previous tx # - SENDALL to multiple different contracts in a single tx, from a single or multiple contracts, # all of which would not self destruct (or perhaps some of them would and some others won't) # Recursive contract creation and self-destruction @@ -77,9 +71,9 @@ def sendall_recipient_address() -> int: @pytest.fixture def sendall_recipient_addresses( sendall_recipient_address: int, -) -> List[int]: +) -> List[str]: """List of possible addresses that can receive a SENDALL operation.""" - return [sendall_recipient_address] + return [to_address(sendall_recipient_address)] def selfdestruct_code_preset( @@ -174,7 +168,7 @@ def pre( selfdestruct_contract_address: str, selfdestruct_contract_initial_balance: int, sendall_recipient_address: Optional[int], - sendall_recipient_addresses: List[int], + sendall_recipient_addresses: List[str], ) -> Dict[str, Account]: """Pre-state of all tests""" pre = { @@ -198,9 +192,9 @@ def pre( # Send-all recipient accounts contain code that unconditionally resets an storage key upon # entry, so we can check that it was not executed - for sendall_recipient_address in sendall_recipient_addresses: - assert sendall_recipient_address is not None, "None send-all recipient address" - pre[to_address(sendall_recipient_address)] = Account( + for address in sendall_recipient_addresses: + assert address is not None, "None send-all recipient address" + pre[address] = Account( code=Op.SSTORE(0, 0), storage={0: 1}, ) @@ -601,18 +595,24 @@ def test_recreate_selfdestructed_contract_different_txs( @pytest.mark.parametrize( "call_times,sendall_recipient_addresses", [ - (1, [0x1000]), - (10, [0x1000]), - (10, [0x1000, 0x2000, 0x3000]), + (1, [to_address(0x1000)]), + (10, [to_address(0x1000)]), + (10, [to_address(0x1000), to_address(0x2000), to_address(0x3000)]), + (10, [PRE_EXISTING_SELFDESTRUCT_ADDRESS, to_address(0x2000), to_address(0x3000)]), + (10, [to_address(0x2000), to_address(0x3000), PRE_EXISTING_SELFDESTRUCT_ADDRESS]), ], ids=[ "single_call", "multiple_calls_single_sendall_recipient", "multiple_calls_multiple_sendall_recipients", + "multiple_calls_multiple_sendall_recipients_including_self", + "multiple_calls_multiple_sendall_recipients_including_self_different_order", ], ) @pytest.mark.parametrize("sendall_recipient_address", [None]) # Calldata sendall recipient -@pytest.mark.parametrize("selfdestruct_contract_address", [PRE_EXISTING_SELFDESTRUCT_ADDRESS]) +@pytest.mark.parametrize( + "selfdestruct_contract_address", [PRE_EXISTING_SELFDESTRUCT_ADDRESS], ids=["pre_existing"] +) @pytest.mark.parametrize("eip_enabled", [True, False]) @pytest.mark.valid_from("Shanghai") def test_selfdestruct_pre_existing( @@ -624,7 +624,7 @@ def test_selfdestruct_pre_existing( selfdestruct_contract_address: str, selfdestruct_code: bytes, selfdestruct_contract_initial_balance: int, - sendall_recipient_addresses: List[int], + sendall_recipient_addresses: List[str], call_times: int, ): """ @@ -694,8 +694,9 @@ def test_selfdestruct_pre_existing( } # Check the balances of the sendall recipients + # TODO: This is incorrect if the recipient is self for address, balance in sendall_final_balances.items(): - post[to_address(address)] = Account(balance=balance, storage={0: 1}) + post[address] = Account(balance=balance, storage={0: 1}) if eip_enabled: post[selfdestruct_contract_address] = Account( From 3fc1cc1cf247ef2fd36c326f8ba875f207c549f1 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Tue, 11 Jul 2023 23:37:30 +0000 Subject: [PATCH 30/38] tests/6780: All tests accept sendall address list --- .../eip6780_selfdestruct/test_selfdestruct.py | 84 +++++++++---------- 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index 59fc672f09..1d518acb58 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -63,22 +63,14 @@ def env() -> Environment: @pytest.fixture -def sendall_recipient_address() -> int: - """Account that receives the balance from the self-destructed account.""" - return 0x1234 - - -@pytest.fixture -def sendall_recipient_addresses( - sendall_recipient_address: int, -) -> List[str]: +def sendall_recipient_addresses() -> List[str]: """List of possible addresses that can receive a SENDALL operation.""" - return [to_address(sendall_recipient_address)] + return [to_address(0x1234)] def selfdestruct_code_preset( *, - sendall_recipient_address: Optional[int], + sendall_recipient_addresses: List[str], ) -> bytes: """Return a bytecode that self-destructs.""" @@ -89,10 +81,13 @@ def selfdestruct_code_preset( ) # Do the self-destruct, either using the recipient address from calldata or a preset one - if sendall_recipient_address is None: - bytecode += Op.SELFDESTRUCT(Op.CALLDATALOAD(0)) + if len(sendall_recipient_addresses) == 1: + # Hard-code the single only possible recipient address + bytecode += Op.SELFDESTRUCT(Op.PUSH20(sendall_recipient_addresses[0])) else: - bytecode += Op.SELFDESTRUCT(sendall_recipient_address) + # Load the recipient address from calldata, each test case needs to pass the adresses as + # calldata + bytecode += Op.SELFDESTRUCT(Op.CALLDATALOAD(0)) # This should never be reached, even when the contract is not self-destructed bytecode += Op.SSTORE(0, 0) @@ -102,14 +97,14 @@ def selfdestruct_code_preset( @pytest.fixture def selfdestruct_code( - sendall_recipient_address: Optional[int], + sendall_recipient_addresses: List[str], ) -> bytes: """ Creates the default self-destructing bytecode, which can be modified by each test if necessary. """ return selfdestruct_code_preset( - sendall_recipient_address=sendall_recipient_address, + sendall_recipient_addresses=sendall_recipient_addresses, ) @@ -167,7 +162,6 @@ def pre( selfdestruct_contract_initcode: bytes, selfdestruct_contract_address: str, selfdestruct_contract_initial_balance: int, - sendall_recipient_address: Optional[int], sendall_recipient_addresses: List[str], ) -> Dict[str, Account]: """Pre-state of all tests""" @@ -185,7 +179,7 @@ def pre( # Also put a pre-existing copy of the self-destruct contract in a known place pre[PRE_EXISTING_SELFDESTRUCT_ADDRESS] = Account( code=selfdestruct_code_preset( - sendall_recipient_address=sendall_recipient_address, + sendall_recipient_addresses=sendall_recipient_addresses, ), balance=selfdestruct_contract_initial_balance, ) @@ -215,7 +209,7 @@ def test_create_selfdestruct_same_tx( selfdestruct_code: bytes, selfdestruct_contract_initcode: bytes, selfdestruct_contract_address: str, - sendall_recipient_address: int, + sendall_recipient_addresses: List[str], initcode_copy_from_address: str, create_opcode: Op, call_times: int, @@ -223,7 +217,13 @@ def test_create_selfdestruct_same_tx( ): # Our entry point is an initcode that in turn creates a self-destructing contract entry_code_storage = Storage() - sendall_amount = 0 + + # Final balances of the sendall recipients + sendall_final_balances = dict( + zip(sendall_recipient_addresses, [0] * len(sendall_recipient_addresses)) + ) + # Selfdestruct contract initial balance will be sent to the first sendall recipient + sendall_final_balances[sendall_recipient_addresses[0]] = selfdestruct_contract_initial_balance # Bytecode used to create the contract, can be CREATE or CREATE2 create_args = [ @@ -265,7 +265,8 @@ def test_create_selfdestruct_same_tx( # Call the self-destructing contract multiple times as required, incrementing the wei sent each # time - for i in range(call_times): + for i, sendall_recipient in zip(range(call_times), cycle(sendall_recipient_addresses)): + entry_code += Op.MSTORE(0, Op.PUSH20(sendall_recipient)) entry_code += Op.SSTORE( entry_code_storage.store_next(1), Op.CALL( @@ -273,14 +274,13 @@ def test_create_selfdestruct_same_tx( Op.PUSH20(selfdestruct_contract_address), # Address i, # Value 0, - 0, + 32, 0, 0, ), ) - # TODO: Why is this correct ?? - sendall_amount += i + sendall_final_balances[sendall_recipient] += i entry_code += Op.SSTORE( entry_code_storage.store_next(0), @@ -302,11 +302,6 @@ def test_create_selfdestruct_same_tx( # values for verification. entry_code += Op.RETURN(len(selfdestruct_contract_initcode), 1) - if selfdestruct_contract_initial_balance > 0: - # Address where the contract is created already had some balance, - # which must be included in the send-all operation - sendall_amount += selfdestruct_contract_initial_balance - post: Dict[str, Account] = { entry_code_address: Account( code="0x00", @@ -316,9 +311,13 @@ def test_create_selfdestruct_same_tx( initcode_copy_from_address: Account( code=selfdestruct_contract_initcode, ), - to_address(sendall_recipient_address): Account(balance=sendall_amount, storage={0: 1}), } + # Check the balances of the sendall recipients + # TODO: This is incorrect if the recipient is self + for address, balance in sendall_final_balances.items(): + post[address] = Account(balance=balance, storage={0: 1}) + nonce = count() tx = Transaction( ty=0x0, @@ -348,7 +347,7 @@ def test_selfdestructing_initcode( entry_code_address: str, selfdestruct_contract_initcode: bytes, selfdestruct_contract_address: str, - sendall_recipient_address: int, + sendall_recipient_addresses: List[str], initcode_copy_from_address: str, create_opcode: Op, call_times: int, # Number of times to call the self-destructing contract in the same tx @@ -435,7 +434,7 @@ def test_selfdestructing_initcode( initcode_copy_from_address: Account( code=selfdestruct_contract_initcode, ), - to_address(sendall_recipient_address): Account(balance=sendall_amount, storage={0: 1}), + sendall_recipient_addresses[0]: Account(balance=sendall_amount, storage={0: 1}), } nonce = count() @@ -468,7 +467,7 @@ def test_selfdestructing_initcode_create_tx( entry_code_address: str, selfdestruct_contract_initcode: bytes, selfdestruct_contract_address: str, - sendall_recipient_address: int, + sendall_recipient_addresses: List[str], initcode_copy_from_address: str, selfdestruct_contract_initial_balance: int, ): @@ -482,7 +481,7 @@ def test_selfdestructing_initcode_create_tx( initcode_copy_from_address: Account( code=selfdestruct_contract_initcode, ), - to_address(sendall_recipient_address): Account(balance=sendall_amount, storage={0: 1}), + sendall_recipient_addresses[0]: Account(balance=sendall_amount, storage={0: 1}), } nonce = count() @@ -515,7 +514,7 @@ def test_recreate_selfdestructed_contract_different_txs( selfdestruct_contract_initcode: bytes, selfdestruct_contract_address: str, selfdestruct_contract_initial_balance: int, - sendall_recipient_address: int, + sendall_recipient_addresses: List[str], initcode_copy_from_address: str, create_opcode: Op, recreate_times: int, # Number of times to recreate the contract in different transactions @@ -585,7 +584,7 @@ def test_recreate_selfdestructed_contract_different_txs( initcode_copy_from_address: Account( code=selfdestruct_contract_initcode, ), - to_address(sendall_recipient_address): Account(balance=sendall_amount, storage={0: 1}), + sendall_recipient_addresses[0]: Account(balance=sendall_amount, storage={0: 1}), } blockchain_test(genesis_environment=env, pre=pre, post=post, blocks=[Block(txs=txs)]) @@ -609,7 +608,6 @@ def test_recreate_selfdestructed_contract_different_txs( "multiple_calls_multiple_sendall_recipients_including_self_different_order", ], ) -@pytest.mark.parametrize("sendall_recipient_address", [None]) # Calldata sendall recipient @pytest.mark.parametrize( "selfdestruct_contract_address", [PRE_EXISTING_SELFDESTRUCT_ADDRESS], ids=["pre_existing"] ) @@ -739,7 +737,7 @@ def test_selfdestruct_created_same_block_different_tx( selfdestruct_code: bytes, selfdestruct_contract_initcode: bytes, selfdestruct_contract_initial_balance: int, - sendall_recipient_address: int, + sendall_recipient_addresses: List[str], call_times: int, ): """ @@ -799,7 +797,7 @@ def test_selfdestruct_created_same_block_different_tx( code="0x00", storage=entry_code_storage, ), - to_address(sendall_recipient_address): Account(balance=sendall_amount, storage={0: 1}), + sendall_recipient_addresses[0]: Account(balance=sendall_amount, storage={0: 1}), } if eip_enabled: @@ -874,7 +872,7 @@ def test_delegatecall_from_new_contract_to_pre_existing_contract( selfdestruct_code: bytes, selfdestruct_contract_initcode: bytes, selfdestruct_contract_address: str, - sendall_recipient_address: int, + sendall_recipient_addresses: List[str], initcode_copy_from_address: str, create_opcode: Op, call_times: int, @@ -975,7 +973,7 @@ def test_delegatecall_from_new_contract_to_pre_existing_contract( initcode_copy_from_address: Account( code=selfdestruct_contract_initcode, ), - to_address(sendall_recipient_address): Account(balance=sendall_amount, storage={0: 1}), + sendall_recipient_addresses[0]: Account(balance=sendall_amount, storage={0: 1}), } nonce = count() @@ -1009,7 +1007,7 @@ def test_delegatecall_from_pre_existing_contract_to_new_contract( selfdestruct_code: bytes, selfdestruct_contract_initcode: bytes, selfdestruct_contract_address: str, - sendall_recipient_address: int, + sendall_recipient_addresses: List[str], initcode_copy_from_address: str, call_opcode: Op, create_opcode: Op, @@ -1124,7 +1122,7 @@ def test_delegatecall_from_pre_existing_contract_to_new_contract( initcode_copy_from_address: Account( code=selfdestruct_contract_initcode, ), - to_address(sendall_recipient_address): Account(balance=sendall_amount, storage={0: 1}), + sendall_recipient_addresses[0]: Account(balance=sendall_amount, storage={0: 1}), } if eip_enabled: From 25a5073c535d61d5a1ceb7be35846e05c40c7e16 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Wed, 12 Jul 2023 00:43:01 +0000 Subject: [PATCH 31/38] tests/6780: Add sendall-to-self to more tests --- .../eip6780_selfdestruct/test_selfdestruct.py | 88 ++++++++++++++----- 1 file changed, 65 insertions(+), 23 deletions(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index 1d518acb58..cc97e61134 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -33,6 +33,14 @@ SELFDESTRUCT_EIP_NUMBER = 6780 PRE_EXISTING_SELFDESTRUCT_ADDRESS = "0x1111111111111111111111111111111111111111" +""" +Address of a pre-existing contract that self-destructs. +""" + +SELFDESTRUCT_CONTRACT_ADDRESS = "" +""" +Sentinel object to indicate that the self-destructing contract address should be used. +""" # TODO: @@ -81,13 +89,16 @@ def selfdestruct_code_preset( ) # Do the self-destruct, either using the recipient address from calldata or a preset one - if len(sendall_recipient_addresses) == 1: - # Hard-code the single only possible recipient address - bytecode += Op.SELFDESTRUCT(Op.PUSH20(sendall_recipient_addresses[0])) - else: + if ( + len(sendall_recipient_addresses) != 1 + or SELFDESTRUCT_CONTRACT_ADDRESS in sendall_recipient_addresses + ): # Load the recipient address from calldata, each test case needs to pass the adresses as # calldata bytecode += Op.SELFDESTRUCT(Op.CALLDATALOAD(0)) + else: + # Hard-code the single only possible recipient address + bytecode += Op.SELFDESTRUCT(Op.PUSH20(sendall_recipient_addresses[0])) # This should never be reached, even when the contract is not self-destructed bytecode += Op.SSTORE(0, 0) @@ -186,18 +197,40 @@ def pre( # Send-all recipient accounts contain code that unconditionally resets an storage key upon # entry, so we can check that it was not executed - for address in sendall_recipient_addresses: - assert address is not None, "None send-all recipient address" - pre[address] = Account( - code=Op.SSTORE(0, 0), - storage={0: 1}, - ) + for i in range(len(sendall_recipient_addresses)): + if sendall_recipient_addresses[i] == SELFDESTRUCT_CONTRACT_ADDRESS: + sendall_recipient_addresses[i] = selfdestruct_contract_address + address = sendall_recipient_addresses[i] + if ( + address != PRE_EXISTING_SELFDESTRUCT_ADDRESS + and address != selfdestruct_contract_address + ): + pre[address] = Account( + code=Op.SSTORE(0, 0), + storage={0: 1}, + ) return pre @pytest.mark.parametrize("create_opcode", [Op.CREATE, Op.CREATE2]) -@pytest.mark.parametrize("call_times", [1, 40]) +@pytest.mark.parametrize( + "call_times,sendall_recipient_addresses", + [ + (1, [to_address(0x1000)]), + (10, [to_address(0x1000)]), + (10, [to_address(0x1000), to_address(0x2000), to_address(0x3000)]), + (10, [SELFDESTRUCT_CONTRACT_ADDRESS, to_address(0x2000), to_address(0x3000)]), + (10, [to_address(0x1000), to_address(0x2000), SELFDESTRUCT_CONTRACT_ADDRESS]), + ], + ids=[ + "single_call", + "multiple_calls_single_sendall_recipient", + "multiple_calls_multiple_sendall_recipients", + "multiple_calls_multiple_sendall_recipients_including_self", + "multiple_calls_multiple_sendall_recipients_including_self_different_order", + ], +) @pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 100_000]) @pytest.mark.parametrize("eip_enabled", [True, False]) @pytest.mark.valid_from("Shanghai") @@ -300,24 +333,24 @@ def test_create_selfdestruct_same_tx( # Lastly return "0x00" so the entry point contract is created and we can retain the stored # values for verification. - entry_code += Op.RETURN(len(selfdestruct_contract_initcode), 1) + entry_code += Op.RETURN(max(len(selfdestruct_contract_initcode), 32), 1) post: Dict[str, Account] = { entry_code_address: Account( code="0x00", storage=entry_code_storage, ), - selfdestruct_contract_address: Account.NONEXISTENT, # type: ignore initcode_copy_from_address: Account( code=selfdestruct_contract_initcode, ), } # Check the balances of the sendall recipients - # TODO: This is incorrect if the recipient is self for address, balance in sendall_final_balances.items(): post[address] = Account(balance=balance, storage={0: 1}) + post[selfdestruct_contract_address] = Account.NONEXISTENT # type: ignore + nonce = count() tx = Transaction( ty=0x0, @@ -418,7 +451,7 @@ def test_selfdestructing_initcode( # Lastly return "0x00" so the entry point contract is created and we can retain the stored # values for verification. - entry_code += Op.RETURN(len(selfdestruct_contract_initcode), 1) + entry_code += Op.RETURN(max(len(selfdestruct_contract_initcode), 32), 1) if selfdestruct_contract_initial_balance > 0: # Address where the contract is created already had some balance, @@ -557,7 +590,7 @@ def test_recreate_selfdestructed_contract_different_txs( ) sendall_amount += i - entry_code += Op.RETURN(len(selfdestruct_contract_initcode), 1) + entry_code += Op.RETURN(max(len(selfdestruct_contract_initcode), 32), 1) txs: List[Transaction] = [] nonce = count() @@ -590,24 +623,26 @@ def test_recreate_selfdestructed_contract_different_txs( blockchain_test(genesis_environment=env, pre=pre, post=post, blocks=[Block(txs=txs)]) -@pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 1]) @pytest.mark.parametrize( "call_times,sendall_recipient_addresses", [ (1, [to_address(0x1000)]), + (1, [PRE_EXISTING_SELFDESTRUCT_ADDRESS]), (10, [to_address(0x1000)]), (10, [to_address(0x1000), to_address(0x2000), to_address(0x3000)]), (10, [PRE_EXISTING_SELFDESTRUCT_ADDRESS, to_address(0x2000), to_address(0x3000)]), - (10, [to_address(0x2000), to_address(0x3000), PRE_EXISTING_SELFDESTRUCT_ADDRESS]), + (10, [to_address(0x1000), to_address(0x2000), PRE_EXISTING_SELFDESTRUCT_ADDRESS]), ], ids=[ "single_call", + "single_call_self_sendall_recipient", "multiple_calls_single_sendall_recipient", "multiple_calls_multiple_sendall_recipients", "multiple_calls_multiple_sendall_recipients_including_self", "multiple_calls_multiple_sendall_recipients_including_self_different_order", ], ) +@pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 100_000]) @pytest.mark.parametrize( "selfdestruct_contract_address", [PRE_EXISTING_SELFDESTRUCT_ADDRESS], ids=["pre_existing"] ) @@ -682,7 +717,7 @@ def test_selfdestruct_pre_existing( # Lastly return "0x00" so the entry point contract is created and we can retain the stored # values for verification. - entry_code += Op.RETURN(0, 1) + entry_code += Op.RETURN(32, 1) post: Dict[str, Account] = { entry_code_address: Account( @@ -697,8 +732,15 @@ def test_selfdestruct_pre_existing( post[address] = Account(balance=balance, storage={0: 1}) if eip_enabled: + balance = ( + sendall_final_balances[selfdestruct_contract_address] + if selfdestruct_contract_address in sendall_final_balances + else 0 + ) post[selfdestruct_contract_address] = Account( - balance=0, code=selfdestruct_code, storage={0: call_times} + balance=balance, + code=selfdestruct_code, + storage={0: call_times}, ) else: post[selfdestruct_contract_address] = Account.NONEXISTENT # type: ignore @@ -790,7 +832,7 @@ def test_selfdestruct_created_same_block_different_tx( # Lastly return "0x00" so the entry point contract is created and we can retain the stored # values for verification. - entry_code += Op.RETURN(0, 1) + entry_code += Op.RETURN(32, 1) post: Dict[str, Account] = { entry_code_address: Account( @@ -957,7 +999,7 @@ def test_delegatecall_from_new_contract_to_pre_existing_contract( # Lastly return "0x00" so the entry point contract is created and we can retain the stored # values for verification. - entry_code += Op.RETURN(len(selfdestruct_contract_initcode), 1) + entry_code += Op.RETURN(max(len(selfdestruct_contract_initcode), 32), 1) if selfdestruct_contract_initial_balance > 0: # Address where the contract is created already had some balance, @@ -1109,7 +1151,7 @@ def test_delegatecall_from_pre_existing_contract_to_new_contract( # Lastly return "0x00" so the entry point contract is created and we can retain the stored # values for verification. - entry_code += Op.RETURN(len(selfdestruct_contract_initcode), 1) + entry_code += Op.RETURN(max(len(selfdestruct_contract_initcode), 32), 1) post: Dict[str, Account] = { entry_code_address: Account( From 59e4ed716923c543454de76c56aa12211abc7e2a Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Wed, 12 Jul 2023 01:15:15 +0000 Subject: [PATCH 32/38] fix: tox --- .wordlist_opcodes.txt | 1 + .../eip6780_selfdestruct/test_selfdestruct.py | 130 ++++++++++++------ 2 files changed, 92 insertions(+), 39 deletions(-) diff --git a/.wordlist_opcodes.txt b/.wordlist_opcodes.txt index 59efe47ace..774a30ac41 100644 --- a/.wordlist_opcodes.txt +++ b/.wordlist_opcodes.txt @@ -36,6 +36,7 @@ calldatacopy codesize codecopy gasprice +extcode extcodesize extcodecopy returndatasize diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index cc97e61134..dccad265a6 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -6,7 +6,7 @@ """ # noqa: E501 from itertools import count, cycle -from typing import Dict, List, Optional +from typing import Dict, List import pytest from ethereum.crypto.hash import keccak256 @@ -47,7 +47,7 @@ # - Create and destroy multiple contracts in the same tx # - Create multiple contracts in a tx and destroy only one of them, but attempt to destroy the # other one in a subsequent tx -# - Create a contract and try to destroy using another calltype (e.g. Delegatecall then destroy) +# - Create a contract and try to destroy using another call type (e.g. Delegatecall then destroy) # - Create a contract that creates another contract, then selfdestruct only the parent # (or vice versa) # - SENDALL to multiple different contracts in a single tx, from a single or multiple contracts, @@ -81,11 +81,10 @@ def selfdestruct_code_preset( sendall_recipient_addresses: List[str], ) -> bytes: """Return a bytecode that self-destructs.""" - # First save into store the count of how many times we've re-entered the contract bytecode = Op.SSTORE( 0, - Op.ADD(Op.SLOAD(0), 1), # Add to the SSTORE'd value each time we enter the contract + Op.ADD(Op.SLOAD(0), 1), # Add to the SSTORE value each time we enter the contract ) # Do the self-destruct, either using the recipient address from calldata or a preset one @@ -93,7 +92,7 @@ def selfdestruct_code_preset( len(sendall_recipient_addresses) != 1 or SELFDESTRUCT_CONTRACT_ADDRESS in sendall_recipient_addresses ): - # Load the recipient address from calldata, each test case needs to pass the adresses as + # Load the recipient address from calldata, each test case needs to pass the addresses as # calldata bytecode += Op.SELFDESTRUCT(Op.CALLDATALOAD(0)) else: @@ -120,7 +119,7 @@ def selfdestruct_code( @pytest.fixture -def selfdestructing_initcode() -> bool: +def self_destructing_initcode() -> bool: """ Whether the contract shall self-destruct during initialization. By default it does not. @@ -131,10 +130,10 @@ def selfdestructing_initcode() -> bool: @pytest.fixture def selfdestruct_contract_initcode( selfdestruct_code: bytes, - selfdestructing_initcode: bool, + self_destructing_initcode: bool, ) -> bytes: """Prepares an initcode that creates a self-destructing account.""" - if selfdestructing_initcode: + if self_destructing_initcode: return selfdestruct_code return Initcode(deploy_code=selfdestruct_code).assemble() @@ -248,6 +247,17 @@ def test_create_selfdestruct_same_tx( call_times: int, selfdestruct_contract_initial_balance: int, ): + """ + Use CREATE or CREATE2 to create a self-destructing contract, and call it in the same + transaction. + + Behavior should be the same before and after EIP-6780. + + Test using: + - Different send-all recipient addresses: single, multiple, including self + - Different initial balances for the self-destructing contract + - Different opcodes: CREATE, CREATE2 + """ # Our entry point is an initcode that in turn creates a self-destructing contract entry_code_storage = Storage() @@ -285,7 +295,7 @@ def test_create_selfdestruct_same_tx( ) ) - # Store the extcode* properties of the created address + # Store the EXTCODE* properties of the created address entry_code += Op.SSTORE( entry_code_storage.store_next(len(selfdestruct_code)), Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), @@ -296,7 +306,7 @@ def test_create_selfdestruct_same_tx( Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), ) - # Call the self-destructing contract multiple times as required, incrementing the wei sent each + # Call the self-destructing contract multiple times as required, increasing the wei sent each # time for i, sendall_recipient in zip(range(call_times), cycle(sendall_recipient_addresses)): entry_code += Op.MSTORE(0, Op.PUSH20(sendall_recipient)) @@ -320,7 +330,7 @@ def test_create_selfdestruct_same_tx( Op.BALANCE(Op.PUSH20(selfdestruct_contract_address)), ) - # Check the extcode* properties of the selfdestructing contract again + # Check the EXTCODE* properties of the self-destructing contract again entry_code += Op.SSTORE( entry_code_storage.store_next(len(selfdestruct_code)), Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), @@ -331,7 +341,7 @@ def test_create_selfdestruct_same_tx( Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), ) - # Lastly return "0x00" so the entry point contract is created and we can retain the stored + # 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) @@ -370,10 +380,10 @@ def test_create_selfdestruct_same_tx( @pytest.mark.parametrize("create_opcode", [Op.CREATE, Op.CREATE2]) @pytest.mark.parametrize("call_times", [0, 1]) @pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 100_000]) -@pytest.mark.parametrize("selfdestructing_initcode", [True], ids=[""]) +@pytest.mark.parametrize("self_destructing_initcode", [True], ids=[""]) @pytest.mark.parametrize("eip_enabled", [True, False]) @pytest.mark.valid_from("Shanghai") -def test_selfdestructing_initcode( +def test_self_destructing_initcode( state_test: StateTestFiller, env: Environment, pre: Dict[str, Account], @@ -386,6 +396,16 @@ def test_selfdestructing_initcode( call_times: int, # Number of times to call the self-destructing contract in the same tx selfdestruct_contract_initial_balance: int, ): + """ + Test that a contract can self-destruct in its initcode. + + Behavior is the same before and after EIP-6780. + + Test using: + - Different initial balances for the self-destructing contract + - Different opcodes: CREATE, CREATE2 + - Different number of calls to the self-destructing contract in the same tx + """ # Our entry point is an initcode that in turn creates a self-destructing contract entry_code_storage = Storage() sendall_amount = 0 @@ -417,7 +437,7 @@ def test_selfdestructing_initcode( ) ) - # Store the extcode* properties of the created address + # Store the EXTCODE* properties of the created address entry_code += Op.SSTORE( entry_code_storage.store_next(0), Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), @@ -428,7 +448,7 @@ def test_selfdestructing_initcode( Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), ) - # Call the self-destructing contract multiple times as required, incrementing the wei sent each + # Call the self-destructing contract multiple times as required, increasing the wei sent each # time for i in range(call_times): entry_code += Op.SSTORE( @@ -449,7 +469,7 @@ def test_selfdestructing_initcode( Op.BALANCE(Op.PUSH20(selfdestruct_contract_address)), ) - # Lastly return "0x00" so the entry point contract is created and we can retain the stored + # 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) @@ -488,11 +508,11 @@ def test_selfdestructing_initcode( @pytest.mark.parametrize("tx_value", [0, 100_000]) @pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 100_000]) -@pytest.mark.parametrize("selfdestructing_initcode", [True], ids=[""]) @pytest.mark.parametrize("selfdestruct_contract_address", [compute_create_address(TestAddress, 0)]) +@pytest.mark.parametrize("self_destructing_initcode", [True], ids=[""]) @pytest.mark.parametrize("eip_enabled", [True, False]) @pytest.mark.valid_from("Shanghai") -def test_selfdestructing_initcode_create_tx( +def test_self_destructing_initcode_create_tx( state_test: StateTestFiller, env: Environment, pre: Dict[str, Account], @@ -504,6 +524,15 @@ def test_selfdestructing_initcode_create_tx( initcode_copy_from_address: str, selfdestruct_contract_initial_balance: int, ): + """ + Use a Create Transaction to execute a self-destructing initcode. + + Behavior should be the same before and after EIP-6780. + + Test using: + - Different initial balances for the self-destructing contract + - Different transaction value amounts + """ assert entry_code_address == selfdestruct_contract_address # Our entry point is an initcode that in turn creates a self-destructing contract @@ -539,7 +568,7 @@ def test_selfdestructing_initcode_create_tx( @pytest.mark.parametrize("call_times", [1]) @pytest.mark.parametrize("eip_enabled", [True, False]) @pytest.mark.valid_from("Shanghai") -def test_recreate_selfdestructed_contract_different_txs( +def test_recreate_self_destructed_contract_different_txs( blockchain_test: BlockchainTestFiller, env: Environment, pre: Dict[str, Account], @@ -554,7 +583,14 @@ def test_recreate_selfdestructed_contract_different_txs( call_times: int, # Number of times to call the self-destructing contract in the same tx ): """ - Test that a contract can be recreated after it has self-destructed. + Test that a contract can be recreated after it has self-destructed, over the lapse + of multiple transactions. + + Behavior should be the same before and after EIP-6780. + + Test using: + - Different initial balances for the self-destructing contract + - CREATE2 only """ entry_code_storage = Storage() sendall_amount = selfdestruct_contract_initial_balance @@ -661,8 +697,15 @@ def test_selfdestruct_pre_existing( call_times: int, ): """ - Test that if a previously created account that contains a selfdestruct is - called, its balance is sent to the destination address. + Test calling a previously created account that contains a selfdestruct, and verify its balance + is sent to the destination address. + + After EIP-6780, the balance should be sent to the send-all recipient address, similar to + the behavior before the EIP, but the account is not deleted. + + Test using: + - Different send-all recipient addresses: single, multiple, including self + - Different initial balances for the self-destructing contract """ entry_code_storage = Storage() @@ -673,11 +716,11 @@ def test_selfdestruct_pre_existing( # Selfdestruct contract initial balance will be sent to the first sendall recipient sendall_final_balances[sendall_recipient_addresses[0]] = selfdestruct_contract_initial_balance - # Entry code in this case will simply call the pre-existing selfdestructing contract, + # Entry code in this case will simply call the pre-existing self-destructing contract, # as many times as required entry_code = b"" - # Call the self-destructing contract multiple times as required, incrementing the wei sent each + # Call the self-destructing contract multiple times as required, increasing the wei sent each # time for i, sendall_recipient in zip(range(call_times), cycle(sendall_recipient_addresses)): entry_code += Op.MSTORE(0, Op.PUSH20(sendall_recipient)) @@ -701,7 +744,7 @@ def test_selfdestruct_pre_existing( Op.BALANCE(Op.PUSH20(selfdestruct_contract_address)), ) - # Check the extcode* properties of the selfdestructing contract + # Check the EXTCODE* properties of the self-destructing contract entry_code += Op.SSTORE( entry_code_storage.store_next(len(selfdestruct_code)), Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), @@ -715,7 +758,7 @@ def test_selfdestruct_pre_existing( Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), ) - # Lastly return "0x00" so the entry point contract is created and we can retain the stored + # Lastly return zero so the entry point contract is created and we can retain the stored # values for verification. entry_code += Op.RETURN(32, 1) @@ -784,16 +827,16 @@ def test_selfdestruct_created_same_block_different_tx( ): """ Test that if an account created in the same block that contains a selfdestruct is - called, its balance is sent to the zero address. + called, its balance is sent to the send-all address, but the account is not deleted. """ entry_code_storage = Storage() sendall_amount = selfdestruct_contract_initial_balance entry_code = b"" - # Entry code in this case will simply call the pre-existing selfdestructing contract, + # Entry code in this case will simply call the pre-existing self-destructing contract, # as many times as required - # Call the self-destructing contract multiple times as required, incrementing the wei sent each + # Call the self-destructing contract multiple times as required, increasing the wei sent each # time for i in range(call_times): entry_code += Op.SSTORE( @@ -816,7 +859,7 @@ def test_selfdestruct_created_same_block_different_tx( Op.BALANCE(Op.PUSH20(selfdestruct_contract_address)), ) - # Check the extcode* properties of the selfdestructing contract + # Check the EXTCODE* properties of the self-destructing contract entry_code += Op.SSTORE( entry_code_storage.store_next(len(selfdestruct_code)), Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), @@ -830,7 +873,7 @@ def test_selfdestruct_created_same_block_different_tx( Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), ) - # Lastly return "0x00" so the entry point contract is created and we can retain the stored + # Lastly return zero so the entry point contract is created and we can retain the stored # values for verification. entry_code += Op.RETURN(32, 1) @@ -920,6 +963,10 @@ def test_delegatecall_from_new_contract_to_pre_existing_contract( call_times: int, selfdestruct_contract_initial_balance: int, ): + """ + Test that if an account created in the current transaction delegate-call a previously created + account that executes self-destruct, the calling account is deleted. + """ # Our entry point is an initcode that in turn creates a self-destructing contract entry_code_storage = Storage() sendall_amount = 0 @@ -951,7 +998,7 @@ def test_delegatecall_from_new_contract_to_pre_existing_contract( ) ) - # Store the extcode* properties of the created address + # Store the EXTCODE* properties of the created address entry_code += Op.SSTORE( entry_code_storage.store_next(len(selfdestruct_code)), Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), @@ -962,7 +1009,7 @@ def test_delegatecall_from_new_contract_to_pre_existing_contract( Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), ) - # Call the self-destructing contract multiple times as required, incrementing the wei sent each + # Call the self-destructing contract multiple times as required, increasing the wei sent each # time for i in range(call_times): entry_code += Op.SSTORE( @@ -986,7 +1033,7 @@ def test_delegatecall_from_new_contract_to_pre_existing_contract( Op.BALANCE(Op.PUSH20(selfdestruct_contract_address)), ) - # Check the extcode* properties of the selfdestructing contract again + # Check the EXTCODE* properties of the self-destructing contract again entry_code += Op.SSTORE( entry_code_storage.store_next(len(selfdestruct_code)), Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), @@ -997,7 +1044,7 @@ def test_delegatecall_from_new_contract_to_pre_existing_contract( Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), ) - # Lastly return "0x00" so the entry point contract is created and we can retain the stored + # 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) @@ -1056,6 +1103,11 @@ def test_delegatecall_from_pre_existing_contract_to_new_contract( call_times: int, selfdestruct_contract_initial_balance: int, ): + """ + Test that if an account created in the current transaction contains a self-destruct and is + delegate-called by an account created before the current transaction, the calling account + is not deleted. + """ # Add the contract that delegate calls to the newly created contract delegate_caller_address = "0x2222222222222222222222222222222222222222" call_args: List[int | bytes] = [ @@ -1103,7 +1155,7 @@ def test_delegatecall_from_pre_existing_contract_to_new_contract( ) ) - # Store the extcode* properties of the pre-existing address + # Store the EXTCODE* properties of the pre-existing address entry_code += Op.SSTORE( entry_code_storage.store_next(len(delegate_caller_code)), Op.EXTCODESIZE(Op.PUSH20(delegate_caller_address)), @@ -1138,7 +1190,7 @@ def test_delegatecall_from_pre_existing_contract_to_new_contract( Op.BALANCE(Op.PUSH20(delegate_caller_address)), ) - # Check the extcode* properties of the pre-existing address again + # Check the EXTCODE* properties of the pre-existing address again entry_code += Op.SSTORE( entry_code_storage.store_next(len(selfdestruct_code)), Op.EXTCODESIZE(Op.PUSH20(delegate_caller_address)), @@ -1149,7 +1201,7 @@ def test_delegatecall_from_pre_existing_contract_to_new_contract( Op.EXTCODEHASH(Op.PUSH20(delegate_caller_address)), ) - # Lastly return "0x00" so the entry point contract is created and we can retain the stored + # 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) From b02c4391e0e49c52225849099583b09cecffe6ac Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Tue, 18 Jul 2023 23:25:08 +0000 Subject: [PATCH 33/38] tests/6780: improve readability using pytest.param --- .../eip6780_selfdestruct/test_selfdestruct.py | 127 ++++++++++++------ 1 file changed, 83 insertions(+), 44 deletions(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index dccad265a6..ea1e642bda 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -216,18 +216,31 @@ def pre( @pytest.mark.parametrize( "call_times,sendall_recipient_addresses", [ - (1, [to_address(0x1000)]), - (10, [to_address(0x1000)]), - (10, [to_address(0x1000), to_address(0x2000), to_address(0x3000)]), - (10, [SELFDESTRUCT_CONTRACT_ADDRESS, to_address(0x2000), to_address(0x3000)]), - (10, [to_address(0x1000), to_address(0x2000), SELFDESTRUCT_CONTRACT_ADDRESS]), - ], - ids=[ - "single_call", - "multiple_calls_single_sendall_recipient", - "multiple_calls_multiple_sendall_recipients", - "multiple_calls_multiple_sendall_recipients_including_self", - "multiple_calls_multiple_sendall_recipients_including_self_different_order", + pytest.param( + 1, + [to_address(0x1000)], + id="single_call", + ), + pytest.param( + 10, + [to_address(0x1000)], + id="multiple_calls_single_sendall_recipient", + ), + pytest.param( + 10, + [to_address(0x1000), to_address(0x2000), to_address(0x3000)], + id="multiple_calls_multiple_sendall_recipients", + ), + pytest.param( + 10, + [SELFDESTRUCT_CONTRACT_ADDRESS, to_address(0x2000), to_address(0x3000)], + id="multiple_calls_multiple_sendall_recipients_including_self", + ), + pytest.param( + 10, + [to_address(0x1000), to_address(0x2000), SELFDESTRUCT_CONTRACT_ADDRESS], + id="multiple_calls_multiple_sendall_recipients_including_self_different_order", + ), ], ) @pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 100_000]) @@ -261,7 +274,7 @@ def test_create_selfdestruct_same_tx( # Our entry point is an initcode that in turn creates a self-destructing contract entry_code_storage = Storage() - # Final balances of the sendall recipients + # Create a dict to record the expected final balances sendall_final_balances = dict( zip(sendall_recipient_addresses, [0] * len(sendall_recipient_addresses)) ) @@ -662,20 +675,41 @@ def test_recreate_self_destructed_contract_different_txs( @pytest.mark.parametrize( "call_times,sendall_recipient_addresses", [ - (1, [to_address(0x1000)]), - (1, [PRE_EXISTING_SELFDESTRUCT_ADDRESS]), - (10, [to_address(0x1000)]), - (10, [to_address(0x1000), to_address(0x2000), to_address(0x3000)]), - (10, [PRE_EXISTING_SELFDESTRUCT_ADDRESS, to_address(0x2000), to_address(0x3000)]), - (10, [to_address(0x1000), to_address(0x2000), PRE_EXISTING_SELFDESTRUCT_ADDRESS]), - ], - ids=[ - "single_call", - "single_call_self_sendall_recipient", - "multiple_calls_single_sendall_recipient", - "multiple_calls_multiple_sendall_recipients", - "multiple_calls_multiple_sendall_recipients_including_self", - "multiple_calls_multiple_sendall_recipients_including_self_different_order", + pytest.param( + 1, + [to_address(0x1000)], + id="single_call", + ), + pytest.param( + 1, + [PRE_EXISTING_SELFDESTRUCT_ADDRESS], + id="single_call_self_sendall_recipient", + ), + pytest.param( + 10, + [to_address(0x1000)], + id="multiple_calls_single_sendall_recipient", + ), + pytest.param( + 10, + [to_address(0x1000), to_address(0x2000), to_address(0x3000)], + id="multiple_calls_multiple_sendall_recipients", + ), + pytest.param( + 10, + [PRE_EXISTING_SELFDESTRUCT_ADDRESS, to_address(0x2000), to_address(0x3000)], + id="multiple_calls_multiple_sendall_recipients_including_self", + ), + pytest.param( + 10, + [to_address(0x1000), to_address(0x2000), PRE_EXISTING_SELFDESTRUCT_ADDRESS], + id="multiple_calls_multiple_sendall_recipients_including_self_different_order", + ), + pytest.param( + 3, + [to_address(0x1000), to_address(0x2000), PRE_EXISTING_SELFDESTRUCT_ADDRESS], + id="multiple_calls_multiple_sendall_recipients_including_self_last", + ), ], ) @pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 100_000]) @@ -709,7 +743,7 @@ def test_selfdestruct_pre_existing( """ entry_code_storage = Storage() - # Final balances of the sendall recipients + # Create a dict to record the expected final balances sendall_final_balances = dict( zip(sendall_recipient_addresses, [0] * len(sendall_recipient_addresses)) ) @@ -924,25 +958,30 @@ def test_selfdestruct_created_same_block_different_tx( @pytest.mark.parametrize( "selfdestruct_code", [ - Op.DELEGATECALL( - Op.GAS, - Op.PUSH20(PRE_EXISTING_SELFDESTRUCT_ADDRESS), - 0, - 0, - 0, - 0, + pytest.param( + Op.DELEGATECALL( + Op.GAS, + Op.PUSH20(PRE_EXISTING_SELFDESTRUCT_ADDRESS), + 0, + 0, + 0, + 0, + ), + id="delegatecall", ), - Op.CALLCODE( - Op.GAS, - Op.PUSH20(PRE_EXISTING_SELFDESTRUCT_ADDRESS), - 0, - 0, - 0, - 0, - 0, + pytest.param( + Op.CALLCODE( + Op.GAS, + Op.PUSH20(PRE_EXISTING_SELFDESTRUCT_ADDRESS), + 0, + 0, + 0, + 0, + 0, + ), + id="callcode", ), ], - ids=["delegatecall", "callcode"], ) # The self-destruct code is delegatecall @pytest.mark.parametrize("call_times", [1]) @pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 1]) From 5f8ce1b76bfdbf2bf9adae674c2d8e90ffe2ee33 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Wed, 19 Jul 2023 17:11:15 +0000 Subject: [PATCH 34/38] tests/6780: two more test cases to same tx test --- tests/cancun/eip6780_selfdestruct/test_selfdestruct.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index ea1e642bda..3a2e038326 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -221,6 +221,11 @@ def pre( [to_address(0x1000)], id="single_call", ), + pytest.param( + 1, + [SELFDESTRUCT_CONTRACT_ADDRESS], + id="single_call_self", + ), pytest.param( 10, [to_address(0x1000)], @@ -241,6 +246,11 @@ def pre( [to_address(0x1000), to_address(0x2000), SELFDESTRUCT_CONTRACT_ADDRESS], id="multiple_calls_multiple_sendall_recipients_including_self_different_order", ), + pytest.param( + 3, + [to_address(0x1000), to_address(0x2000), SELFDESTRUCT_CONTRACT_ADDRESS], + id="multiple_calls_multiple_sendall_recipients_including_self_last", + ), ], ) @pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 100_000]) From 04c60c399620acdb52e553171cfeecf1114205f7 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Wed, 19 Jul 2023 20:34:00 +0000 Subject: [PATCH 35/38] tests/6780: use yul to conditionally destroy the contract --- .../eip6780_selfdestruct/test_selfdestruct.py | 201 +++++++++++------- 1 file changed, 119 insertions(+), 82 deletions(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index 3a2e038326..e25132cbe1 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -6,7 +6,7 @@ """ # noqa: E501 from itertools import count, cycle -from typing import Dict, List +from typing import Dict, List, SupportsBytes import pytest from ethereum.crypto.hash import keccak256 @@ -21,9 +21,11 @@ Storage, TestAddress, Transaction, + YulCompiler, compute_create2_address, compute_create_address, to_address, + to_hash_bytes, ) from ethereum_test_tools.vm.opcode import Opcodes as Op @@ -37,10 +39,11 @@ Address of a pre-existing contract that self-destructs. """ -SELFDESTRUCT_CONTRACT_ADDRESS = "" -""" -Sentinel object to indicate that the self-destructing contract address should be used. -""" +# Sentinel value to indicate that the self-destructing contract address should be used, only for +# use in parametrization, not for use within the test method itself. +SELF_ADDRESS = "0x1" +# Sentinel value to indicate that the contract should not self-destruct. +NO_SELFDESTRUCT = "0x0" # TODO: @@ -79,42 +82,61 @@ def sendall_recipient_addresses() -> List[str]: def selfdestruct_code_preset( *, sendall_recipient_addresses: List[str], -) -> bytes: + yul: YulCompiler, +) -> SupportsBytes: """Return a bytecode that self-destructs.""" - # First save into store the count of how many times we've re-entered the contract - bytecode = Op.SSTORE( - 0, - Op.ADD(Op.SLOAD(0), 1), # Add to the SSTORE value each time we enter the contract - ) # Do the self-destruct, either using the recipient address from calldata or a preset one - if ( - len(sendall_recipient_addresses) != 1 - or SELFDESTRUCT_CONTRACT_ADDRESS in sendall_recipient_addresses - ): + if len(sendall_recipient_addresses) != 1: # Load the recipient address from calldata, each test case needs to pass the addresses as # calldata - bytecode += Op.SELFDESTRUCT(Op.CALLDATALOAD(0)) + return yul( + f""" + {{ + sstore(0, add(sload(0), 1)) + let selfdestruct_recipient := calldataload(0) + if eq(selfdestruct_recipient, {SELF_ADDRESS}) {{ + // One sends to self + selfdestruct_recipient := address() + }} + if not(eq(selfdestruct_recipient, {NO_SELFDESTRUCT})) {{ + // zero is the sentinel value for not self-destructing + selfdestruct(selfdestruct_recipient) + sstore(0, 0) + }} + }} + """ + ) else: # Hard-code the single only possible recipient address - bytecode += Op.SELFDESTRUCT(Op.PUSH20(sendall_recipient_addresses[0])) - - # This should never be reached, even when the contract is not self-destructed - bytecode += Op.SSTORE(0, 0) - - return bytecode + sendall_recipient = sendall_recipient_addresses[0] + assert sendall_recipient != NO_SELFDESTRUCT, "test error" + if sendall_recipient == SELF_ADDRESS: + # Use the self address of the contract we are creating + sendall_recipient = "address()" + return yul( + f""" + {{ + sstore(0, add(sload(0), 1)) + selfdestruct({sendall_recipient_addresses[0]}) + sstore(0, 0) + }} + """ + ) @pytest.fixture def selfdestruct_code( sendall_recipient_addresses: List[str], -) -> bytes: + yul: YulCompiler, +) -> SupportsBytes: """ Creates the default self-destructing bytecode, which can be modified by each test if necessary. """ return selfdestruct_code_preset( sendall_recipient_addresses=sendall_recipient_addresses, + yul=yul, ) @@ -129,13 +151,13 @@ def self_destructing_initcode() -> bool: @pytest.fixture def selfdestruct_contract_initcode( - selfdestruct_code: bytes, + selfdestruct_code: SupportsBytes, self_destructing_initcode: bool, -) -> bytes: +) -> SupportsBytes: """Prepares an initcode that creates a self-destructing account.""" if self_destructing_initcode: return selfdestruct_code - return Initcode(deploy_code=selfdestruct_code).assemble() + return Initcode(deploy_code=selfdestruct_code) @pytest.fixture @@ -154,7 +176,7 @@ def entry_code_address() -> str: def selfdestruct_contract_address( create_opcode: Op, entry_code_address: str, - selfdestruct_contract_initcode: bytes, + selfdestruct_contract_initcode: SupportsBytes, ) -> str: """Returns the address of the self-destructing contract.""" if create_opcode == Op.CREATE: @@ -169,10 +191,11 @@ def selfdestruct_contract_address( @pytest.fixture def pre( initcode_copy_from_address: str, - selfdestruct_contract_initcode: bytes, + selfdestruct_contract_initcode: SupportsBytes, selfdestruct_contract_address: str, selfdestruct_contract_initial_balance: int, sendall_recipient_addresses: List[str], + yul: YulCompiler, ) -> Dict[str, Account]: """Pre-state of all tests""" pre = { @@ -190,6 +213,7 @@ def pre( pre[PRE_EXISTING_SELFDESTRUCT_ADDRESS] = Account( code=selfdestruct_code_preset( sendall_recipient_addresses=sendall_recipient_addresses, + yul=yul, ), balance=selfdestruct_contract_initial_balance, ) @@ -197,7 +221,7 @@ def pre( # Send-all recipient accounts contain code that unconditionally resets an storage key upon # entry, so we can check that it was not executed for i in range(len(sendall_recipient_addresses)): - if sendall_recipient_addresses[i] == SELFDESTRUCT_CONTRACT_ADDRESS: + if sendall_recipient_addresses[i] == SELF_ADDRESS: sendall_recipient_addresses[i] = selfdestruct_contract_address address = sendall_recipient_addresses[i] if ( @@ -223,7 +247,7 @@ def pre( ), pytest.param( 1, - [SELFDESTRUCT_CONTRACT_ADDRESS], + [SELF_ADDRESS], id="single_call_self", ), pytest.param( @@ -238,17 +262,17 @@ def pre( ), pytest.param( 10, - [SELFDESTRUCT_CONTRACT_ADDRESS, to_address(0x2000), to_address(0x3000)], + [SELF_ADDRESS, to_address(0x2000), to_address(0x3000)], id="multiple_calls_multiple_sendall_recipients_including_self", ), pytest.param( 10, - [to_address(0x1000), to_address(0x2000), SELFDESTRUCT_CONTRACT_ADDRESS], + [to_address(0x1000), to_address(0x2000), SELF_ADDRESS], id="multiple_calls_multiple_sendall_recipients_including_self_different_order", ), pytest.param( 3, - [to_address(0x1000), to_address(0x2000), SELFDESTRUCT_CONTRACT_ADDRESS], + [to_address(0x1000), to_address(0x2000), SELF_ADDRESS], id="multiple_calls_multiple_sendall_recipients_including_self_last", ), ], @@ -261,8 +285,8 @@ def test_create_selfdestruct_same_tx( env: Environment, pre: Dict[str, Account], entry_code_address: str, - selfdestruct_code: bytes, - selfdestruct_contract_initcode: bytes, + selfdestruct_code: SupportsBytes, + selfdestruct_contract_initcode: SupportsBytes, selfdestruct_contract_address: str, sendall_recipient_addresses: List[str], initcode_copy_from_address: str, @@ -295,7 +319,7 @@ def test_create_selfdestruct_same_tx( create_args = [ 0, # Value 0, # Offset - len(selfdestruct_contract_initcode), # Length + len(bytes(selfdestruct_contract_initcode)), # Length ] if create_opcode == Op.CREATE2: # CREATE2 requires a salt argument @@ -309,7 +333,7 @@ def test_create_selfdestruct_same_tx( Op.PUSH20(initcode_copy_from_address), 0, 0, - len(selfdestruct_contract_initcode), + len(bytes(selfdestruct_contract_initcode)), ) # And we store the created address for verification purposes + Op.SSTORE( @@ -320,12 +344,12 @@ def test_create_selfdestruct_same_tx( # Store the EXTCODE* properties of the created address entry_code += Op.SSTORE( - entry_code_storage.store_next(len(selfdestruct_code)), + entry_code_storage.store_next(len(bytes(selfdestruct_code))), Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), ) entry_code += Op.SSTORE( - entry_code_storage.store_next(keccak256(selfdestruct_code)), + entry_code_storage.store_next(keccak256(bytes(selfdestruct_code))), Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), ) @@ -355,18 +379,18 @@ def test_create_selfdestruct_same_tx( # Check the EXTCODE* properties of the self-destructing contract again entry_code += Op.SSTORE( - entry_code_storage.store_next(len(selfdestruct_code)), + entry_code_storage.store_next(len(bytes(selfdestruct_code))), Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), ) entry_code += Op.SSTORE( - entry_code_storage.store_next(keccak256(selfdestruct_code)), + entry_code_storage.store_next(keccak256(bytes(selfdestruct_code))), Op.EXTCODEHASH(Op.PUSH20(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) + entry_code += Op.RETURN(max(len(bytes(selfdestruct_contract_initcode)), 32), 1) post: Dict[str, Account] = { entry_code_address: Account( @@ -411,7 +435,7 @@ def test_self_destructing_initcode( env: Environment, pre: Dict[str, Account], entry_code_address: str, - selfdestruct_contract_initcode: bytes, + selfdestruct_contract_initcode: SupportsBytes, selfdestruct_contract_address: str, sendall_recipient_addresses: List[str], initcode_copy_from_address: str, @@ -437,7 +461,7 @@ def test_self_destructing_initcode( create_args = [ 0, # Value 0, # Offset - len(selfdestruct_contract_initcode), # Length + len(bytes(selfdestruct_contract_initcode)), # Length ] if create_opcode == Op.CREATE2: # CREATE2 requires a salt argument @@ -451,7 +475,7 @@ def test_self_destructing_initcode( Op.PUSH20(initcode_copy_from_address), 0, 0, - len(selfdestruct_contract_initcode), + len(bytes(selfdestruct_contract_initcode)), ) # And we store the created address for verification purposes + Op.SSTORE( @@ -494,7 +518,7 @@ def test_self_destructing_initcode( # 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) + entry_code += Op.RETURN(max(len(bytes(selfdestruct_contract_initcode)), 32), 1) if selfdestruct_contract_initial_balance > 0: # Address where the contract is created already had some balance, @@ -541,7 +565,7 @@ def test_self_destructing_initcode_create_tx( pre: Dict[str, Account], tx_value: int, entry_code_address: str, - selfdestruct_contract_initcode: bytes, + selfdestruct_contract_initcode: SupportsBytes, selfdestruct_contract_address: str, sendall_recipient_addresses: List[str], initcode_copy_from_address: str, @@ -586,6 +610,19 @@ def test_self_destructing_initcode_create_tx( @pytest.mark.parametrize("create_opcode", [Op.CREATE2]) # Can only recreate using CREATE2 +@pytest.mark.parametrize( + "sendall_recipient_addresses", + [ + pytest.param( + [to_address(0x1000)], + id="selfdestruct_other_address", + ), + pytest.param( + [SELF_ADDRESS], + id="selfdestruct_to_self", + ), + ], +) @pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 100_000]) @pytest.mark.parametrize("recreate_times", [1]) @pytest.mark.parametrize("call_times", [1]) @@ -596,7 +633,7 @@ def test_recreate_self_destructed_contract_different_txs( env: Environment, pre: Dict[str, Account], entry_code_address: str, - selfdestruct_contract_initcode: bytes, + selfdestruct_contract_initcode: SupportsBytes, selfdestruct_contract_address: str, selfdestruct_contract_initial_balance: int, sendall_recipient_addresses: List[str], @@ -620,7 +657,7 @@ def test_recreate_self_destructed_contract_different_txs( # Bytecode used to create the contract assert create_opcode == Op.CREATE2, "cannot recreate contract using CREATE opcode" - create_bytecode = Op.CREATE2(0, 0, len(selfdestruct_contract_initcode), 0) + create_bytecode = Op.CREATE2(0, 0, len(bytes(selfdestruct_contract_initcode)), 0) # Entry code that will be executed, creates the contract and then calls it entry_code = ( @@ -629,10 +666,10 @@ def test_recreate_self_destructed_contract_different_txs( Op.PUSH20(initcode_copy_from_address), 0, 0, - len(selfdestruct_contract_initcode), + len(bytes(selfdestruct_contract_initcode)), ) + Op.SSTORE( - Op.ADD(Op.SLOAD(0), 1), + Op.CALLDATALOAD(0), create_bytecode, ) ) @@ -649,7 +686,7 @@ def test_recreate_self_destructed_contract_different_txs( ) sendall_amount += i - entry_code += Op.RETURN(max(len(selfdestruct_contract_initcode), 32), 1) + entry_code += Op.STOP txs: List[Transaction] = [] nonce = count() @@ -657,27 +694,30 @@ def test_recreate_self_destructed_contract_different_txs( txs.append( Transaction( ty=0x0, - data=entry_code, + data=to_hash_bytes(i), chain_id=0x0, nonce=next(nonce), - to=entry_code_address if i > 0 else None, # First call creates the contract + to=entry_code_address, gas_limit=100_000_000, gas_price=10, protected=False, ) ) + entry_code_storage[i] = selfdestruct_contract_address + pre[entry_code_address] = Account(code=entry_code) post: Dict[str, Account] = { entry_code_address: Account( - code="0x00", + code=entry_code, storage=entry_code_storage, ), selfdestruct_contract_address: Account.NONEXISTENT, # type: ignore initcode_copy_from_address: Account( code=selfdestruct_contract_initcode, ), - sendall_recipient_addresses[0]: Account(balance=sendall_amount, storage={0: 1}), } + if sendall_recipient_addresses[0] != selfdestruct_contract_address: + post[sendall_recipient_addresses[0]] = Account(balance=sendall_amount, storage={0: 1}) blockchain_test(genesis_environment=env, pre=pre, post=post, blocks=[Block(txs=txs)]) @@ -735,7 +775,7 @@ def test_selfdestruct_pre_existing( pre: Dict[str, Account], entry_code_address: str, selfdestruct_contract_address: str, - selfdestruct_code: bytes, + selfdestruct_code: SupportsBytes, selfdestruct_contract_initial_balance: int, sendall_recipient_addresses: List[str], call_times: int, @@ -790,15 +830,12 @@ def test_selfdestruct_pre_existing( # Check the EXTCODE* properties of the self-destructing contract entry_code += Op.SSTORE( - entry_code_storage.store_next(len(selfdestruct_code)), + entry_code_storage.store_next(len(bytes(selfdestruct_code))), Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), ) entry_code += Op.SSTORE( - # entry_code_storage.store_next(keccak256(selfdestruct_code if eip_enabled else b"")) - # TODO: Don't really understand why this works. It should be empty if EIP is disabled, - # but it works if it's not - entry_code_storage.store_next(keccak256(selfdestruct_code)), + entry_code_storage.store_next(keccak256(bytes(selfdestruct_code))), Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), ) @@ -863,8 +900,8 @@ def test_selfdestruct_created_same_block_different_tx( pre: Dict[str, Account], entry_code_address: str, selfdestruct_contract_address: str, - selfdestruct_code: bytes, - selfdestruct_contract_initcode: bytes, + selfdestruct_code: SupportsBytes, + selfdestruct_contract_initcode: SupportsBytes, selfdestruct_contract_initial_balance: int, sendall_recipient_addresses: List[str], call_times: int, @@ -905,7 +942,7 @@ def test_selfdestruct_created_same_block_different_tx( # Check the EXTCODE* properties of the self-destructing contract entry_code += Op.SSTORE( - entry_code_storage.store_next(len(selfdestruct_code)), + entry_code_storage.store_next(len(bytes(selfdestruct_code))), Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), ) @@ -913,7 +950,7 @@ def test_selfdestruct_created_same_block_different_tx( # entry_code_storage.store_next(keccak256(selfdestruct_code if eip_enabled else b"")) # TODO: Don't really understand why this works. It should be empty if EIP is disabled, # but it works if it's not - entry_code_storage.store_next(keccak256(selfdestruct_code)), + entry_code_storage.store_next(keccak256(bytes(selfdestruct_code))), Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), ) @@ -1003,8 +1040,8 @@ def test_delegatecall_from_new_contract_to_pre_existing_contract( env: Environment, pre: Dict[str, Account], entry_code_address: str, - selfdestruct_code: bytes, - selfdestruct_contract_initcode: bytes, + selfdestruct_code: SupportsBytes, + selfdestruct_contract_initcode: SupportsBytes, selfdestruct_contract_address: str, sendall_recipient_addresses: List[str], initcode_copy_from_address: str, @@ -1024,7 +1061,7 @@ def test_delegatecall_from_new_contract_to_pre_existing_contract( create_args = [ 0, # Value 0, # Offset - len(selfdestruct_contract_initcode), # Length + len(bytes(selfdestruct_contract_initcode)), # Length ] if create_opcode == Op.CREATE2: # CREATE2 requires a salt argument @@ -1038,7 +1075,7 @@ def test_delegatecall_from_new_contract_to_pre_existing_contract( Op.PUSH20(initcode_copy_from_address), 0, 0, - len(selfdestruct_contract_initcode), + len(bytes(selfdestruct_contract_initcode)), ) # And we store the created address for verification purposes + Op.SSTORE( @@ -1049,12 +1086,12 @@ def test_delegatecall_from_new_contract_to_pre_existing_contract( # Store the EXTCODE* properties of the created address entry_code += Op.SSTORE( - entry_code_storage.store_next(len(selfdestruct_code)), + entry_code_storage.store_next(len(bytes(selfdestruct_code))), Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), ) entry_code += Op.SSTORE( - entry_code_storage.store_next(keccak256(selfdestruct_code)), + entry_code_storage.store_next(keccak256(bytes(selfdestruct_code))), Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), ) @@ -1084,18 +1121,18 @@ def test_delegatecall_from_new_contract_to_pre_existing_contract( # Check the EXTCODE* properties of the self-destructing contract again entry_code += Op.SSTORE( - entry_code_storage.store_next(len(selfdestruct_code)), + entry_code_storage.store_next(len(bytes(selfdestruct_code))), Op.EXTCODESIZE(Op.PUSH20(selfdestruct_contract_address)), ) entry_code += Op.SSTORE( - entry_code_storage.store_next(keccak256(selfdestruct_code)), + entry_code_storage.store_next(keccak256(bytes(selfdestruct_code))), Op.EXTCODEHASH(Op.PUSH20(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) + entry_code += Op.RETURN(max(len(bytes(selfdestruct_contract_initcode)), 32), 1) if selfdestruct_contract_initial_balance > 0: # Address where the contract is created already had some balance, @@ -1142,8 +1179,8 @@ def test_delegatecall_from_pre_existing_contract_to_new_contract( env: Environment, pre: Dict[str, Account], entry_code_address: str, - selfdestruct_code: bytes, - selfdestruct_contract_initcode: bytes, + selfdestruct_code: SupportsBytes, + selfdestruct_contract_initcode: SupportsBytes, selfdestruct_contract_address: str, sendall_recipient_addresses: List[str], initcode_copy_from_address: str, @@ -1181,7 +1218,7 @@ def test_delegatecall_from_pre_existing_contract_to_new_contract( create_args = [ 0, # Value 0, # Offset - len(selfdestruct_contract_initcode), # Length + len(bytes(selfdestruct_contract_initcode)), # Length ] if create_opcode == Op.CREATE2: # CREATE2 requires a salt argument @@ -1195,7 +1232,7 @@ def test_delegatecall_from_pre_existing_contract_to_new_contract( Op.PUSH20(initcode_copy_from_address), 0, 0, - len(selfdestruct_contract_initcode), + len(bytes(selfdestruct_contract_initcode)), ) # And we store the created address for verification purposes + Op.SSTORE( @@ -1241,18 +1278,18 @@ def test_delegatecall_from_pre_existing_contract_to_new_contract( # Check the EXTCODE* properties of the pre-existing address again entry_code += Op.SSTORE( - entry_code_storage.store_next(len(selfdestruct_code)), + entry_code_storage.store_next(len(bytes(delegate_caller_code))), Op.EXTCODESIZE(Op.PUSH20(delegate_caller_address)), ) entry_code += Op.SSTORE( - entry_code_storage.store_next(keccak256(selfdestruct_code)), + entry_code_storage.store_next(keccak256(bytes(delegate_caller_code))), Op.EXTCODEHASH(Op.PUSH20(delegate_caller_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) + entry_code += Op.RETURN(max(len(bytes(selfdestruct_contract_initcode)), 32), 1) post: Dict[str, Account] = { entry_code_address: Account( From ec0a40cfd6fcc58ed55fdfa3286b95ee4acbce2d Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Tue, 25 Jul 2023 19:17:39 +0000 Subject: [PATCH 36/38] tests/6780: Fix test cases + always enable in Cancun --- .../eip6780_selfdestruct/test_selfdestruct.py | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index e25132cbe1..09196f495b 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -11,6 +11,7 @@ import pytest from ethereum.crypto.hash import keccak256 +from ethereum_test_forks import Cancun, Fork, is_fork from ethereum_test_tools import ( Account, Block, @@ -32,7 +33,7 @@ REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6780.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" -SELFDESTRUCT_EIP_NUMBER = 6780 +SELFDESTRUCT_ENABLE_FORK = Cancun PRE_EXISTING_SELFDESTRUCT_ADDRESS = "0x1111111111111111111111111111111111111111" """ @@ -40,7 +41,7 @@ """ # Sentinel value to indicate that the self-destructing contract address should be used, only for -# use in parametrization, not for use within the test method itself. +# use in `pytest.mark.parametrize`, not for use within the test method itself. SELF_ADDRESS = "0x1" # Sentinel value to indicate that the contract should not self-destruct. NO_SELFDESTRUCT = "0x0" @@ -59,9 +60,9 @@ @pytest.fixture -def eips(eip_enabled: bool) -> List[int]: - """Prepares the list of EIPs depending on the test that enables it or not.""" - return [SELFDESTRUCT_EIP_NUMBER] if eip_enabled else [] +def eip_enabled(fork: Fork) -> bool: + """Whether the EIP is enabled or not.""" + return is_fork(fork, SELFDESTRUCT_ENABLE_FORK) @pytest.fixture @@ -85,8 +86,6 @@ def selfdestruct_code_preset( yul: YulCompiler, ) -> SupportsBytes: """Return a bytecode that self-destructs.""" - - # Do the self-destruct, either using the recipient address from calldata or a preset one if len(sendall_recipient_addresses) != 1: # Load the recipient address from calldata, each test case needs to pass the addresses as # calldata @@ -278,7 +277,6 @@ def pre( ], ) @pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 100_000]) -@pytest.mark.parametrize("eip_enabled", [True, False]) @pytest.mark.valid_from("Shanghai") def test_create_selfdestruct_same_tx( state_test: StateTestFiller, @@ -312,8 +310,7 @@ def test_create_selfdestruct_same_tx( sendall_final_balances = dict( zip(sendall_recipient_addresses, [0] * len(sendall_recipient_addresses)) ) - # Selfdestruct contract initial balance will be sent to the first sendall recipient - sendall_final_balances[sendall_recipient_addresses[0]] = selfdestruct_contract_initial_balance + selfdestruct_contract_current_balance = selfdestruct_contract_initial_balance # Bytecode used to create the contract, can be CREATE or CREATE2 create_args = [ @@ -369,8 +366,15 @@ def test_create_selfdestruct_same_tx( 0, ), ) + selfdestruct_contract_current_balance += i + + # Balance is always sent to other contracts + if sendall_recipient != selfdestruct_contract_address: + sendall_final_balances[sendall_recipient] += selfdestruct_contract_current_balance - sendall_final_balances[sendall_recipient] += i + # 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), @@ -428,7 +432,6 @@ def test_create_selfdestruct_same_tx( @pytest.mark.parametrize("call_times", [0, 1]) @pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 100_000]) @pytest.mark.parametrize("self_destructing_initcode", [True], ids=[""]) -@pytest.mark.parametrize("eip_enabled", [True, False]) @pytest.mark.valid_from("Shanghai") def test_self_destructing_initcode( state_test: StateTestFiller, @@ -557,7 +560,6 @@ def test_self_destructing_initcode( @pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 100_000]) @pytest.mark.parametrize("selfdestruct_contract_address", [compute_create_address(TestAddress, 0)]) @pytest.mark.parametrize("self_destructing_initcode", [True], ids=[""]) -@pytest.mark.parametrize("eip_enabled", [True, False]) @pytest.mark.valid_from("Shanghai") def test_self_destructing_initcode_create_tx( state_test: StateTestFiller, @@ -626,7 +628,6 @@ def test_self_destructing_initcode_create_tx( @pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 100_000]) @pytest.mark.parametrize("recreate_times", [1]) @pytest.mark.parametrize("call_times", [1]) -@pytest.mark.parametrize("eip_enabled", [True, False]) @pytest.mark.valid_from("Shanghai") def test_recreate_self_destructed_contract_different_txs( blockchain_test: BlockchainTestFiller, @@ -766,7 +767,6 @@ def test_recreate_self_destructed_contract_different_txs( @pytest.mark.parametrize( "selfdestruct_contract_address", [PRE_EXISTING_SELFDESTRUCT_ADDRESS], ids=["pre_existing"] ) -@pytest.mark.parametrize("eip_enabled", [True, False]) @pytest.mark.valid_from("Shanghai") def test_selfdestruct_pre_existing( state_test: StateTestFiller, @@ -797,8 +797,7 @@ def test_selfdestruct_pre_existing( sendall_final_balances = dict( zip(sendall_recipient_addresses, [0] * len(sendall_recipient_addresses)) ) - # Selfdestruct contract initial balance will be sent to the first sendall recipient - sendall_final_balances[sendall_recipient_addresses[0]] = selfdestruct_contract_initial_balance + selfdestruct_contract_current_balance = selfdestruct_contract_initial_balance # Entry code in this case will simply call the pre-existing self-destructing contract, # as many times as required @@ -820,11 +819,19 @@ def test_selfdestruct_pre_existing( 0, ), ) + selfdestruct_contract_current_balance += i + + # Balance is always sent to other contracts + if sendall_recipient != selfdestruct_contract_address: + sendall_final_balances[sendall_recipient] += selfdestruct_contract_current_balance - sendall_final_balances[sendall_recipient] += i + # Balance is only kept by the self-destructing contract if we are sending to self and the + # EIP is activated, otherwise the balance is destroyed + if sendall_recipient != selfdestruct_contract_address or not eip_enabled: + selfdestruct_contract_current_balance = 0 entry_code += Op.SSTORE( - entry_code_storage.store_next(0), + entry_code_storage.store_next(selfdestruct_contract_current_balance), Op.BALANCE(Op.PUSH20(selfdestruct_contract_address)), ) @@ -851,16 +858,12 @@ def test_selfdestruct_pre_existing( } # Check the balances of the sendall recipients - # TODO: This is incorrect if the recipient is self for address, balance in sendall_final_balances.items(): - post[address] = Account(balance=balance, storage={0: 1}) + if address != selfdestruct_contract_address: + post[address] = Account(balance=balance, storage={0: 1}) if eip_enabled: - balance = ( - sendall_final_balances[selfdestruct_contract_address] - if selfdestruct_contract_address in sendall_final_balances - else 0 - ) + balance = selfdestruct_contract_current_balance post[selfdestruct_contract_address] = Account( balance=balance, code=selfdestruct_code, @@ -891,7 +894,6 @@ def test_selfdestruct_pre_existing( "selfdestruct_contract_address,entry_code_address", [(compute_create_address(TestAddress, 0), compute_create_address(TestAddress, 1))], ) -@pytest.mark.parametrize("eip_enabled", [True, False]) @pytest.mark.valid_from("Shanghai") def test_selfdestruct_created_same_block_different_tx( blockchain_test: BlockchainTestFiller, @@ -1033,7 +1035,6 @@ def test_selfdestruct_created_same_block_different_tx( @pytest.mark.parametrize("call_times", [1]) @pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 1]) @pytest.mark.parametrize("create_opcode", [Op.CREATE]) -@pytest.mark.parametrize("eip_enabled", [True, False]) @pytest.mark.valid_from("Shanghai") def test_delegatecall_from_new_contract_to_pre_existing_contract( state_test: StateTestFiller, @@ -1171,7 +1172,6 @@ def test_delegatecall_from_new_contract_to_pre_existing_contract( @pytest.mark.parametrize("call_opcode", [Op.DELEGATECALL, Op.CALLCODE]) @pytest.mark.parametrize("call_times", [1]) @pytest.mark.parametrize("selfdestruct_contract_initial_balance", [0, 1]) -@pytest.mark.parametrize("eip_enabled", [True, False]) @pytest.mark.valid_from("Shanghai") def test_delegatecall_from_pre_existing_contract_to_new_contract( state_test: StateTestFiller, From d3c9b841ac8b06f439b4055dc7ace86f05870f0e Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Tue, 25 Jul 2023 19:21:13 +0000 Subject: [PATCH 37/38] tests/6780: Remove TODO, added issue #227 --- .../cancun/eip6780_selfdestruct/test_selfdestruct.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index 09196f495b..6d685f9a7c 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -46,18 +46,6 @@ # Sentinel value to indicate that the contract should not self-destruct. NO_SELFDESTRUCT = "0x0" -# TODO: - -# - Create and destroy multiple contracts in the same tx -# - Create multiple contracts in a tx and destroy only one of them, but attempt to destroy the -# other one in a subsequent tx -# - Create a contract and try to destroy using another call type (e.g. Delegatecall then destroy) -# - Create a contract that creates another contract, then selfdestruct only the parent -# (or vice versa) -# - SENDALL to multiple different contracts in a single tx, from a single or multiple contracts, -# all of which would not self destruct (or perhaps some of them would and some others won't) -# Recursive contract creation and self-destruction - @pytest.fixture def eip_enabled(fork: Fork) -> bool: From 4791d8d56594da7dd39421be625ba855fa4dab50 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Tue, 25 Jul 2023 19:32:06 +0000 Subject: [PATCH 38/38] tests/6780: Removed TODO comments now clarified --- tests/cancun/eip6780_selfdestruct/test_selfdestruct.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index 6d685f9a7c..0e233c8c01 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -937,9 +937,6 @@ def test_selfdestruct_created_same_block_different_tx( ) entry_code += Op.SSTORE( - # entry_code_storage.store_next(keccak256(selfdestruct_code if eip_enabled else b"")) - # TODO: Don't really understand why this works. It should be empty if EIP is disabled, - # but it works if it's not entry_code_storage.store_next(keccak256(bytes(selfdestruct_code))), Op.EXTCODEHASH(Op.PUSH20(selfdestruct_contract_address)), ) @@ -1100,7 +1097,6 @@ def test_delegatecall_from_new_contract_to_pre_existing_contract( ), ) - # TODO: Why is this correct ?? sendall_amount += i entry_code += Op.SSTORE( @@ -1256,7 +1252,6 @@ def test_delegatecall_from_pre_existing_contract_to_new_contract( ), ) - # TODO: Why is this correct ?? sendall_amount += i entry_code += Op.SSTORE(