35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157 | @pytest.mark.valid_from("Paris")
@pytest.mark.parametrize("first_suicide", [Op.CALL, Op.CALLCODE, Op.DELEGATECALL])
@pytest.mark.parametrize("second_suicide", [Op.CALL, Op.CALLCODE, Op.DELEGATECALL])
def test_reentrancy_selfdestruct_revert(
env: Environment,
fork: Fork,
first_suicide: Op,
second_suicide: Op,
state_test: StateTestFiller,
):
"""
Suicide reentrancy scenario:
Call|Callcode|Delegatecall the contract S.
S self destructs.
Call the revert proxy contract R.
R Calls|Callcode|Delegatecall S.
S self destructs (for the second time).
R reverts (including the effects of the second selfdestruct).
It is expected the S is self destructed after the transaction.
"""
address_to = TestAddress2
address_s = Address(0x1000000000000000000000000000000000000001)
address_r = Address(0x1000000000000000000000000000000000000002)
suicide_d = Address(0x03E8)
def construct_call_s(call_type: Op, money: int):
if call_type in [Op.CALLCODE, Op.CALL]:
return call_type(Op.GAS, address_s, money, 0, 0, 0, 0)
else:
return call_type(Op.GAS, address_s, money, 0, 0, 0)
pre = {
address_to: Account(
balance=1000000000000000000,
nonce=0,
code=Op.SSTORE(1, construct_call_s(first_suicide, 0))
+ Op.SSTORE(2, Op.CALL(Op.GAS, address_r, 0, 0, 0, 0, 0))
+ Op.RETURNDATACOPY(0, 0, Op.RETURNDATASIZE())
+ Op.SSTORE(3, Op.MLOAD(0)),
storage={0x01: 0x0100, 0x02: 0x0100, 0x03: 0x0100},
),
address_s: Account(
balance=3000000000000000000,
nonce=0,
code=Op.SELFDESTRUCT(1000),
storage={},
),
address_r: Account(
balance=5000000000000000000,
nonce=0,
# Send money when calling it suicide second time to make sure the funds not transferred
code=Op.MSTORE(0, Op.ADD(15, construct_call_s(second_suicide, 100)))
+ Op.REVERT(0, 32),
storage={},
),
TestAddress: Account(
balance=7000000000000000000,
nonce=0,
code="0x",
storage={},
),
}
post = {
# Second caller unchanged as call gets reverted
address_r: Account(balance=5000000000000000000, storage={}),
}
if first_suicide in [Op.CALLCODE, Op.DELEGATECALL]:
if fork >= Cancun:
# On Cancun even callcode/delegatecall does not remove the account, so the value remain
post[address_to] = Account(
storage={
0x01: 0x01, # First call to contract S->suicide success
0x02: 0x00, # Second call to contract S->suicide reverted
0x03: 16, # Reverted value to check that revert really worked
},
)
else:
# Callcode executed first suicide from sender. sender is deleted
post[address_to] = Account.NONEXISTENT # type: ignore
# Original suicide account remains in state
post[address_s] = Account(balance=3000000000000000000, storage={})
# Suicide destination
post[suicide_d] = Account(
balance=1000000000000000000,
)
# On Cancun suicide no longer destroys the account from state, just cleans the balance
if first_suicide in [Op.CALL]:
post[address_to] = Account(
storage={
0x01: 0x01, # First call to contract S->suicide success
0x02: 0x00, # Second call to contract S->suicide reverted
0x03: 16, # Reverted value to check that revert really worked
},
)
if fork >= Cancun:
# On Cancun suicide does not remove the account, just sends the balance
post[address_s] = Account(balance=0, code="0x6103e8ff", storage={})
else:
post[address_s] = Account.NONEXISTENT # type: ignore
# Suicide destination
post[suicide_d] = Account(
balance=3000000000000000000,
)
tx = Transaction(
ty=0x0,
chain_id=0x0,
nonce=0,
to=address_to,
gas_price=10,
protected=False,
data="",
gas_limit=500000,
value=0,
)
state_test(env=env, pre=pre, post=post, tx=tx)
|