Skip to content

Commit 51d30bd

Browse files
authored
feat(tests): add reentrancy suicide revert test (#372)
1 parent e07bfbe commit 51d30bd

File tree

2 files changed

+159
-0
lines changed

2 files changed

+159
-0
lines changed

docs/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Test fixtures for use by clients are available for each release on the [Github r
88

99
### 🧪 Test Cases
1010

11+
- 🔀 Add reentrancy suicide revert test ([#372](https://github.com/ethereum/execution-spec-tests/pull/372)).
12+
1113
### 🛠️ Framework
1214

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

0 commit comments

Comments
 (0)