Skip to content

Commit

Permalink
Implement EIP-6780 "SELFDESTRUCT" (#735)
Browse files Browse the repository at this point in the history
Implement the EIP-6780 "SELFDESTRUCT only in same transaction".
https://eips.ethereum.org/EIPS/eip-6780

This EIP changes the functionality of the `SELFDESTRUCT` instruction.
The new functionality will be only to send all Ether in the account
to the beneficiary, except that the current behavior is preserved when
`SELFDESTRUCT` is called in the same transaction a contract was created.
  • Loading branch information
chfast authored Nov 27, 2023
2 parents 0174b53 + 90f4f80 commit 2fb7cc1
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 6 deletions.
3 changes: 3 additions & 0 deletions test/state/account.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ struct Account
/// or it is a newly created temporary account.
bool erasable = false;

/// The account has been created in the current transaction.
bool just_created = false;

evmc_access_status access_status = EVMC_ACCESS_COLD;

[[nodiscard]] bool is_empty() const noexcept
Expand Down
27 changes: 22 additions & 5 deletions test/state/host.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,27 @@ size_t Host::copy_code(const address& addr, size_t code_offset, uint8_t* buffer_

bool Host::selfdestruct(const address& addr, const address& beneficiary) noexcept
{
// Touch beneficiary and transfer all balance to it.
// This may happen multiple times per single account as account's balance
// can be increased with a call following previous selfdestruct.
auto& acc = m_state.get(addr);
m_state.touch(beneficiary).balance += acc.balance;
acc.balance = 0; // Zero balance (this can be the beneficiary).
const auto balance = acc.balance;
auto& beneficiary_acc = m_state.touch(beneficiary);

if (m_rev >= EVMC_CANCUN && !acc.just_created)
{
// EIP-6780:
// "SELFDESTRUCT is executed in a transaction that is not the same
// as the contract invoking SELFDESTRUCT was created"
acc.balance = 0;
beneficiary_acc.balance += balance; // Keep balance if acc is the beneficiary.

// Return "selfdestruct not registered".
// In practice this affects only refunds before Cancun.
return false;
}

// Transfer may happen multiple times per single account as account's balance
// can be increased with a call following previous selfdestruct.
beneficiary_acc.balance += balance;
acc.balance = 0; // Zero balance if acc is the beneficiary.

// Mark the destruction if not done already.
return !std::exchange(acc.destructed, true);
Expand Down Expand Up @@ -184,6 +199,8 @@ evmc::Result Host::create(const evmc_message& msg) noexcept
if (m_rev >= EVMC_SPURIOUS_DRAGON)
new_acc.nonce = 1;

new_acc.just_created = true;

// Clear the new account storage, but keep the access status (from tx access list).
// This is only needed for tests and cannot happen in real networks.
for (auto& [_, v] : new_acc.storage) [[unlikely]]
Expand Down
5 changes: 4 additions & 1 deletion test/state/state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,11 +254,14 @@ std::variant<TransactionReceipt, std::error_code> transition(State& state, const
if (rev >= EVMC_SPURIOUS_DRAGON)
delete_empty_accounts(state);

// Set accounts and their storage access status to cold in the end of transition process
// Post-transaction clean-up.
// - Set accounts and their storage access status to cold.
// - Clear the "just created" account flag.
for (auto& [addr, acc] : state.get_accounts())
{
acc.transient_storage.clear();
acc.access_status = EVMC_ACCESS_COLD;
acc.just_created = false;
for (auto& [key, val] : acc.storage)
{
val.access_status = EVMC_ACCESS_COLD;
Expand Down
1 change: 1 addition & 0 deletions test/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ target_sources(
state_transition_block_test.cpp
state_transition_create_test.cpp
state_transition_eof_test.cpp
state_transition_selfdestruct_test.cpp
state_transition_trace_test.cpp
state_transition_transient_storage_test.cpp
state_transition_tx_test.cpp
Expand Down
48 changes: 48 additions & 0 deletions test/unittests/state_transition_selfdestruct_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2023 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0

#include "../utils/bytecode.hpp"
#include "state_transition.hpp"

using namespace evmc::literals;
using namespace evmone::test;

TEST_F(state_transition, selfdestruct_shanghai)
{
rev = EVMC_SHANGHAI;
tx.to = To;
pre.insert(*tx.to, {.balance = 0x4e, .code = selfdestruct(0xbe_address)});

expect.post[To].exists = false;
expect.post[0xbe_address].balance = 0x4e;
}

TEST_F(state_transition, selfdestruct_cancun)
{
rev = EVMC_CANCUN;
tx.to = To;
pre.insert(*tx.to, {.balance = 0x4e, .code = selfdestruct(0xbe_address)});

expect.post[To].balance = 0;
expect.post[0xbe_address].balance = 0x4e;
}

TEST_F(state_transition, selfdestruct_to_self_cancun)
{
rev = EVMC_CANCUN;
tx.to = To;
pre.insert(*tx.to, {.balance = 0x4e, .code = selfdestruct(To)});

expect.post[To].balance = 0x4e;
}

TEST_F(state_transition, selfdestruct_same_tx_cancun)
{
rev = EVMC_CANCUN;
tx.value = 0x4e;
tx.data = selfdestruct(0xbe_address);
pre.get(Sender).balance += 0x4e;

expect.post[0xbe_address].balance = 0x4e;
}

0 comments on commit 2fb7cc1

Please sign in to comment.