Skip to content

Commit a7d03db

Browse files
committed
Implement EIP-8246: "Remove SELFDESTRUCT Burn"
This EIP removes the "burn" feature of SELFDESTRUCT. If a contract tries to burn ETH the balance is preserved. https://eips.ethereum.org/EIPS/eip-8246
1 parent 3616ba6 commit a7d03db

3 files changed

Lines changed: 104 additions & 7 deletions

File tree

test/state/host.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,13 @@ bool Host::selfdestruct(const address& addr, const address& beneficiary) noexcep
147147
return false;
148148
}
149149

150-
// Transfer may happen multiple times per single account as account's balance
151-
// can be increased with a call following previous selfdestruct.
152-
beneficiary_acc.balance += balance;
153-
acc.balance = 0; // Zero balance if acc is the beneficiary.
150+
if (m_rev < EVMC_AMSTERDAM || beneficiary != addr)
151+
{
152+
// Transfer may happen multiple times per single account as account's balance
153+
// can be increased with a call following previous selfdestruct.
154+
beneficiary_acc.balance += balance;
155+
acc.balance = 0; // Zero balance if acc is the beneficiary (before EIP-8246)
156+
}
154157

155158
// Mark the destruction if not done already.
156159
if (!acc.destructed)

test/state/state.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,9 +230,16 @@ StateDiff State::build_diff(evmc_revision rev) const
230230
{
231231
if (m.destructed)
232232
{
233-
// TODO: This must be done even for just_created
234-
// because destructed may pre-date just_created. Add test to evmone (EEST has it).
235-
diff.deleted_accounts.emplace_back(addr);
233+
if (rev >= EVMC_AMSTERDAM && m.balance != 0)
234+
{
235+
// Preserve the balance of the self-destructed account, no burn (EIP-8246).
236+
diff.modified_accounts.emplace_back(StateDiff::Entry{addr, 0, m.balance});
237+
}
238+
else
239+
{
240+
// Delete account. This must be done also for pre-funded just_created account.
241+
diff.deleted_accounts.emplace_back(addr);
242+
}
236243
continue;
237244
}
238245
if (m.erase_if_empty && rev >= EVMC_SPURIOUS_DRAGON && m.is_empty())

test/unittests/state_transition_selfdestruct_test.cpp

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,94 @@ TEST_F(state_transition, selfdestruct_double_revert)
108108

109109
TEST_F(state_transition, selfdestruct_initcode)
110110
{
111+
rev = EVMC_SHANGHAI;
112+
tx.data = selfdestruct(0xbe_address);
113+
114+
expect.post[compute_create_address(tx.sender, tx.nonce)].exists = false;
115+
expect.post[0xbe_address].exists = false;
116+
}
117+
118+
TEST_F(state_transition, selfdestruct_initcode_amsterdam)
119+
{
120+
// A same-tx-created account that self-destructs ending with a zero balance must not be in the
121+
// final state (EIP-8246). In this test we use initcode.
122+
rev = EVMC_AMSTERDAM;
111123
tx.data = selfdestruct(0xbe_address);
124+
125+
expect.post[compute_create_address(tx.sender, tx.nonce)].exists = false;
126+
expect.post[0xbe_address].exists = false;
127+
}
128+
129+
TEST_F(state_transition, selfdestruct_prefunded)
130+
{
131+
// Although burn is removed in EIP-8246, the deletion of a pre-funded account still happens.
132+
rev = EVMC_CANCUN;
133+
const auto created = compute_create_address(tx.sender, tx.nonce);
134+
pre[created] = {.balance = 1};
135+
tx.data = selfdestruct(0xbe_address); // Transfer to distinct beneficiary.
136+
137+
expect.post[created].exists = false; // Removed, despite pre-existing in the state.
138+
expect.post[0xbe_address].balance = 1; // Pre-funded balance delivered to the beneficiary.
139+
}
140+
141+
TEST_F(state_transition, selfdestruct_prefunded_amsterdam)
142+
{
143+
// Although burn is removed in EIP-8246, the deletion of a pre-funded account still happens.
144+
rev = EVMC_AMSTERDAM;
145+
const auto created = compute_create_address(tx.sender, tx.nonce);
146+
pre[created] = {.balance = 1};
147+
tx.data = selfdestruct(0xbe_address); // Transfer to distinct beneficiary.
148+
149+
expect.post[created].exists = false; // Removed, despite pre-existing in the state.
150+
expect.post[0xbe_address].balance = 1; // Pre-funded balance delivered to the beneficiary.
151+
}
152+
153+
TEST_F(state_transition, selfdestruct_prefunded_burn)
154+
{
155+
// Burn pre-funded ETH by self-destruct to self.
156+
rev = EVMC_CANCUN;
157+
const auto created = compute_create_address(tx.sender, tx.nonce);
158+
pre[created] = {.balance = 1};
159+
tx.data = selfdestruct(created);
160+
161+
expect.post[created].exists = false; // Removed, despite pre-existing in the state.
162+
}
163+
164+
TEST_F(state_transition, selfdestruct_prefunded_burn_amsterdam)
165+
{
166+
// Burn is removed with EIP-8246, the balance must be preserved.
167+
rev = EVMC_AMSTERDAM;
168+
const auto created = compute_create_address(tx.sender, tx.nonce);
169+
pre[created] = {.balance = 1};
170+
tx.data = selfdestruct(created);
171+
172+
expect.post[created].balance = 1; // Balance preserved.
173+
expect.post[created].nonce = 0;
174+
expect.post[created].code = {};
175+
}
176+
177+
TEST_F(state_transition, selfdestruct_sibling_create_then_destruct_amsterdam)
178+
{
179+
// A contract created in one sub-call and self-destructed in a sibling sub-call of the same
180+
// transaction must still be removed (especially its code).
181+
rev = EVMC_AMSTERDAM;
182+
static constexpr auto F = 0xfac0_address; // Factory address.
183+
184+
const auto runtime = selfdestruct(0xbe_address);
185+
const auto initcode = mstore(0, push(runtime)) + ret(32 - runtime.size(), runtime.size());
186+
const auto created = compute_create_address(F, 1);
187+
188+
pre[F] = {.nonce = 1,
189+
.balance = 1,
190+
.code = mstore(0, push(initcode)) +
191+
create().input(32 - initcode.size(), initcode.size()).value(1)};
192+
pre[To] = {.code = call(F).gas(0xffffff) + call(created).gas(0xffffff)};
193+
tx.to = To;
194+
195+
expect.post[created].exists = false; // Created and destructed in the same tx -> removed.
196+
expect.post[0xbe_address].balance = 1; // Funds delivered to the beneficiary.
197+
expect.post[F] = {.nonce = 2, .balance = 0}; // F created one contract and sent it the balance.
198+
expect.post[To] = {};
112199
}
113200

114201
TEST_F(state_transition, massdestruct_shanghai)

0 commit comments

Comments
 (0)