From cb38c585f062c60e3760aad53192daad98b0affe Mon Sep 17 00:00:00 2001 From: Dimitry Kh Date: Mon, 7 Jul 2025 11:04:57 +0200 Subject: [PATCH 1/3] type(tests): convert create suicide store test --- .../create/test_create_suicide_store.py | 158 ++++++++++++++++++ ...CREATE_AcreateB_BSuicide_BStoreFiller.json | 102 ----------- 2 files changed, 158 insertions(+), 102 deletions(-) create mode 100644 tests/frontier/create/test_create_suicide_store.py delete mode 100644 tests/static/state_tests/stCreateTest/CREATE_AcreateB_BSuicide_BStoreFiller.json diff --git a/tests/frontier/create/test_create_suicide_store.py b/tests/frontier/create/test_create_suicide_store.py new file mode 100644 index 00000000000..607c158a4b6 --- /dev/null +++ b/tests/frontier/create/test_create_suicide_store.py @@ -0,0 +1,158 @@ +""" +Test dynamically created address is still callable and perform storage operations +after being called for self destruct in a call. +""" + +from enum import Enum + +import pytest + +from ethereum_test_forks import Byzantium, Fork +from ethereum_test_tools import ( + Account, + Alloc, + Case, + Environment, + Initcode, + StateTestFiller, + Storage, + Switch, + Transaction, + compute_create_address, +) +from ethereum_test_tools import Opcodes as Op + + +class Operation(Enum): + """Enum for created contract actions.""" + + SUICIDE = 1 + ADD_STORAGE = 2 + GET_STORAGE = 3 + + def __int__(self): + """Convert to int.""" + return int(self.value) + + +@pytest.mark.ported_from( + [ + "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stCreateTest/CREATE_AcreateB_BSuicide_BStoreFiller.json", + ], + pr=["https://github.com/ethereum/execution-spec-tests/pull/1867"], + # coverage_missed_reason="Converting solidity code result in following opcode not being used:" + # "PUSH29, DUP4, DUP8, SWAP2, ISZERO, AND, MUL, DIV, CALLVALUE, EXTCODESIZE", +) +@pytest.mark.valid_from("Frontier") +@pytest.mark.with_all_create_opcodes +def test_create_suicide_store( + state_test: StateTestFiller, + fork: Fork, + pre: Alloc, + create_opcode: Op, +): + """ + Create dynamic contract that suicides, then called to push some storage + and then called to return that storage value. + """ + tload_support = fork.valid_opcodes().count(Op.TLOAD) + subcall_storage = 0x12 + suicide_initcode: Initcode = Initcode( + deploy_code=Switch( + cases=[ + Case( + condition=Op.EQ(Op.CALLDATALOAD(0), int(Operation.SUICIDE)), + action=Op.SELFDESTRUCT(0x11), + ), + Case( + condition=Op.EQ(Op.CALLDATALOAD(0), int(Operation.ADD_STORAGE)), + action=Op.SSTORE(1, Op.ADD(Op.SLOAD(1), subcall_storage)) + + ( + Op.TSTORE(1, Op.ADD(Op.TLOAD(1), subcall_storage)) + if tload_support + else Op.STOP + ), + ), + Case( + condition=Op.EQ(Op.CALLDATALOAD(0), int(Operation.GET_STORAGE)), + action=( + Op.MSTORE(0, Op.ADD(Op.SLOAD(1), Op.TLOAD(1))) + if tload_support + else Op.MSTORE(0, Op.SLOAD(1)) + ) + + Op.RETURN(0, 32), + ), + ], + default_action=None, + ) + ) + + sender = pre.fund_eoa() + expect_post = Storage() + + slot_create_result = 0 + slot_after_suicide_sstore_return = 1 + slot_program_success = 2 + create_contract = pre.deploy_contract( + code=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.SSTORE(slot_create_result, create_opcode(size=Op.CALLDATASIZE())) + # Put some storage before suicide + + Op.MSTORE(64, int(Operation.ADD_STORAGE)) + + Op.CALL( + gas=Op.SUB(Op.GAS, 300_000), + address=Op.SLOAD(slot_create_result), + args_offset=64, + args_size=32, + ) + + Op.MSTORE(64, int(Operation.SUICIDE)) + + Op.CALL( + gas=Op.SUB(Op.GAS, 300_000), + address=Op.SLOAD(slot_create_result), + args_offset=64, + args_size=32, + ) + # Put some storage after suicide + + Op.MSTORE(64, int(Operation.ADD_STORAGE)) + + Op.CALL( + gas=Op.SUB(Op.GAS, 300_000), + address=Op.SLOAD(slot_create_result), + args_offset=64, + args_size=32, + ) + + Op.MSTORE(64, int(Operation.GET_STORAGE)) + + Op.CALL( + gas=Op.SUB(Op.GAS, 300_000), + address=Op.SLOAD(0), + args_offset=64, + args_size=32, + ret_offset=100, + ret_size=32, + ) + + Op.SSTORE(slot_after_suicide_sstore_return, Op.MLOAD(100)) + + Op.SSTORE(slot_program_success, 1) + ) + + expected_create_address = compute_create_address( + address=create_contract, nonce=1, initcode=suicide_initcode, opcode=create_opcode + ) + expect_post[slot_create_result] = expected_create_address + expect_post[slot_after_suicide_sstore_return] = ( + subcall_storage * 2 # added value before and after suicide + + (subcall_storage * 2 if tload_support else 0) # tload value added + ) + expect_post[slot_program_success] = 1 + + tx = Transaction( + gas_limit=1_000_000, + to=create_contract, + data=suicide_initcode, + nonce=0, + sender=sender, + protected=fork >= Byzantium, + ) + + post = { + create_contract: Account(storage=expect_post), + expected_create_address: Account.NONEXISTENT, + } + state_test(env=Environment(), pre=pre, post=post, tx=tx) diff --git a/tests/static/state_tests/stCreateTest/CREATE_AcreateB_BSuicide_BStoreFiller.json b/tests/static/state_tests/stCreateTest/CREATE_AcreateB_BSuicide_BStoreFiller.json deleted file mode 100644 index 85bebceedc5..00000000000 --- a/tests/static/state_tests/stCreateTest/CREATE_AcreateB_BSuicide_BStoreFiller.json +++ /dev/null @@ -1,102 +0,0 @@ -{ - "CREATE_AcreateB_BSuicide_BStore" : { - "env" : { - "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", - "currentDifficulty" : "0x020000", - "currentGasLimit" : "10000000", - "currentNumber" : "1", - "currentTimestamp" : "1000" - }, - "expect" : [ - { - "indexes" : { - "data" : -1, - "gas" : -1, - "value" : -1 - }, - "network" : [">=Cancun"], - "result" : { - "b94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { - "storage" : { - "0x00" : "0x01", - "0x01" : "0x0c" - } - } - } - } - ], - "pre" : - { - "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { - "balance" : "1000000000000", - "code" : "", - "nonce" : "0", - "storage" : { - } - }, - "1000000000000000000000000000000000000000" : { - "balance" : "1", - "code" : "", - "nonce" : "0", - "storage" : { - } - }, - "b94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { - "balance" : "1000000000000", - "//00 code" : "pragma solidity ^0.4.0; Better Make it with LLL", - "//01 code" : "contract CSuicide", - "//02 code" : "{", - "//03 code" : " uint v;", - "//04 code" : " function killitself()", - "//05 code" : " {", - "//06 code" : " suicide(0x11);", - "//07 code" : " }", - "//08 code" : " ", - "//09 code" : " function storesmth()", - "//0a code" : " {", - "//0b code" : " v = 12;", - "//0c code" : " }", - "//0d code" : " ", - "//0e code" : " function getsmth() returns(uint)", - "//0f code" : " {", - "//10 code" : " return v;", - "//11 code" : " }", - "//12 code" : "}", - "//13 code" : "", - "//14 code" : "contract CreateTest {", - "//15 code" : " uint res;", - "//16 code" : " uint resV;", - "//17 code" : " function run()", - "//18 code" : " {", - "//19 code" : " CSuicide a = new CSuicide();", - "//1a code" : " a.killitself();", - "//1b code" : " a.storesmth();", - "//1c code" : " resV = a.getsmth();", - "//1d code" : " res = 1;", - "//1e code" : " }", - "//1f code" : "}", - "//20 code" : "", - "code" : ":raw 0x60606040526000357c010000000000000000000000000000000000000000000000000000000090048063c04062261461003c57610037565b610002565b346100025761004e6004805050610050565b005b600060405160c9806101d3833901809050604051809103906000f080156100025790508073ffffffffffffffffffffffffffffffffffffffff166369bda7aa604051817c0100000000000000000000000000000000000000000000000000000000028152600401809050600060405180830381600087803b156100025760325a03f115610002575050508073ffffffffffffffffffffffffffffffffffffffff16633ac6dff3604051817c0100000000000000000000000000000000000000000000000000000000028152600401809050600060405180830381600087803b156100025760325a03f115610002575050508073ffffffffffffffffffffffffffffffffffffffff16636f118b2b600060405160200152604051817c0100000000000000000000000000000000000000000000000000000000028152600401809050602060405180830381600087803b156100025760325a03f115610002575050506040518051906020015060016000508190555060016000600050819055505b5056606060405260b98060106000396000f360606040526000357c0100000000000000000000000000000000000000000000000000000000900480633ac6dff314604d57806369bda7aa14605e5780636f118b2b14606f576049565b6002565b34600257605c60048050506094565b005b34600257606d600480505060a2565b005b34600257607e600480505060a8565b6040518082815260200191505060405180910390f35b600c6000600050819055505b565b6011ff5b565b6000600060005054905060b6565b9056", - "nonce" : "0", - "storage" : { - } - } - }, - "transaction" : { - "// data" : "run()", - "data" : [ - ":raw 0xc0406226" - ], - "gasLimit" : [ - "600000" - ], - "gasPrice" : "10", - "nonce" : "0", - "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", - "to" : "b94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "value" : [ - "0" - ] - } - } -} From f0bf031d78e7e644ed8f9af8fcddbc7d79ba1a47 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Wed, 8 Oct 2025 21:20:31 +0000 Subject: [PATCH 2/3] small improvements --- .../create/test_create_suicide_store.py | 40 ++++++++----------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/tests/frontier/create/test_create_suicide_store.py b/tests/frontier/create/test_create_suicide_store.py index 607c158a4b6..0709c84cb88 100644 --- a/tests/frontier/create/test_create_suicide_store.py +++ b/tests/frontier/create/test_create_suicide_store.py @@ -3,7 +3,7 @@ after being called for self destruct in a call. """ -from enum import Enum +from enum import IntEnum import pytest @@ -11,8 +11,7 @@ from ethereum_test_tools import ( Account, Alloc, - Case, - Environment, + CalldataCase, Initcode, StateTestFiller, Storage, @@ -23,17 +22,13 @@ from ethereum_test_tools import Opcodes as Op -class Operation(Enum): +class Operation(IntEnum): """Enum for created contract actions.""" SUICIDE = 1 ADD_STORAGE = 2 GET_STORAGE = 3 - def __int__(self): - """Convert to int.""" - return int(self.value) - @pytest.mark.ported_from( [ @@ -50,7 +45,7 @@ def test_create_suicide_store( fork: Fork, pre: Alloc, create_opcode: Op, -): +) -> None: """ Create dynamic contract that suicides, then called to push some storage and then called to return that storage value. @@ -60,12 +55,12 @@ def test_create_suicide_store( suicide_initcode: Initcode = Initcode( deploy_code=Switch( cases=[ - Case( - condition=Op.EQ(Op.CALLDATALOAD(0), int(Operation.SUICIDE)), - action=Op.SELFDESTRUCT(0x11), + CalldataCase( + value=Operation.SUICIDE, + action=Op.SELFDESTRUCT(pre.empty_account()), ), - Case( - condition=Op.EQ(Op.CALLDATALOAD(0), int(Operation.ADD_STORAGE)), + CalldataCase( + value=Operation.ADD_STORAGE, action=Op.SSTORE(1, Op.ADD(Op.SLOAD(1), subcall_storage)) + ( Op.TSTORE(1, Op.ADD(Op.TLOAD(1), subcall_storage)) @@ -73,8 +68,8 @@ def test_create_suicide_store( else Op.STOP ), ), - Case( - condition=Op.EQ(Op.CALLDATALOAD(0), int(Operation.GET_STORAGE)), + CalldataCase( + value=Operation.GET_STORAGE, action=( Op.MSTORE(0, Op.ADD(Op.SLOAD(1), Op.TLOAD(1))) if tload_support @@ -94,17 +89,17 @@ def test_create_suicide_store( slot_after_suicide_sstore_return = 1 slot_program_success = 2 create_contract = pre.deploy_contract( - code=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + code=Op.CALLDATACOPY(size=Op.CALLDATASIZE()) + Op.SSTORE(slot_create_result, create_opcode(size=Op.CALLDATASIZE())) # Put some storage before suicide - + Op.MSTORE(64, int(Operation.ADD_STORAGE)) + + Op.MSTORE(64, Operation.ADD_STORAGE) + Op.CALL( gas=Op.SUB(Op.GAS, 300_000), address=Op.SLOAD(slot_create_result), args_offset=64, args_size=32, ) - + Op.MSTORE(64, int(Operation.SUICIDE)) + + Op.MSTORE(64, Operation.SUICIDE) + Op.CALL( gas=Op.SUB(Op.GAS, 300_000), address=Op.SLOAD(slot_create_result), @@ -112,14 +107,14 @@ def test_create_suicide_store( args_size=32, ) # Put some storage after suicide - + Op.MSTORE(64, int(Operation.ADD_STORAGE)) + + Op.MSTORE(64, Operation.ADD_STORAGE) + Op.CALL( gas=Op.SUB(Op.GAS, 300_000), address=Op.SLOAD(slot_create_result), args_offset=64, args_size=32, ) - + Op.MSTORE(64, int(Operation.GET_STORAGE)) + + Op.MSTORE(64, Operation.GET_STORAGE) + Op.CALL( gas=Op.SUB(Op.GAS, 300_000), address=Op.SLOAD(0), @@ -146,7 +141,6 @@ def test_create_suicide_store( gas_limit=1_000_000, to=create_contract, data=suicide_initcode, - nonce=0, sender=sender, protected=fork >= Byzantium, ) @@ -155,4 +149,4 @@ def test_create_suicide_store( create_contract: Account(storage=expect_post), expected_create_address: Account.NONEXISTENT, } - state_test(env=Environment(), pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) From 504ad377db5383d129b1265fbeb1d088956d0188 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Wed, 8 Oct 2025 22:04:43 +0000 Subject: [PATCH 3/3] fix: lint, add coverage change reason --- tests/frontier/create/test_create_suicide_store.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/frontier/create/test_create_suicide_store.py b/tests/frontier/create/test_create_suicide_store.py index 0709c84cb88..31af347195e 100644 --- a/tests/frontier/create/test_create_suicide_store.py +++ b/tests/frontier/create/test_create_suicide_store.py @@ -1,6 +1,6 @@ """ -Test dynamically created address is still callable and perform storage operations -after being called for self destruct in a call. +Test dynamically created address is still callable and perform storage +operations after being called for self destruct in a call. """ from enum import IntEnum @@ -35,8 +35,9 @@ class Operation(IntEnum): "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stCreateTest/CREATE_AcreateB_BSuicide_BStoreFiller.json", ], pr=["https://github.com/ethereum/execution-spec-tests/pull/1867"], - # coverage_missed_reason="Converting solidity code result in following opcode not being used:" - # "PUSH29, DUP4, DUP8, SWAP2, ISZERO, AND, MUL, DIV, CALLVALUE, EXTCODESIZE", + coverage_missed_reason="Converting solidity code result in following opcode not being used:" + "PUSH29, DUP4, DUP8, SWAP2, ISZERO, AND, MUL, DIV, CALLVALUE, EXTCODESIZE." + "Changed 0x11 address to new address (no check for precompile).", ) @pytest.mark.valid_from("Frontier") @pytest.mark.with_all_create_opcodes