diff --git a/test/state/account.hpp b/test/state/account.hpp index 242b4eca7e..a1b3e91947 100644 --- a/test/state/account.hpp +++ b/test/state/account.hpp @@ -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 diff --git a/test/state/host.cpp b/test/state/host.cpp index 4741757aad..6e29eaa8d0 100644 --- a/test/state/host.cpp +++ b/test/state/host.cpp @@ -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); @@ -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]] diff --git a/test/state/state.cpp b/test/state/state.cpp index de7654133a..2b74a76b4f 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -254,11 +254,14 @@ std::variant 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; diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index a08098fa96..4e74c518bd 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -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 diff --git a/test/unittests/state_transition_selfdestruct_test.cpp b/test/unittests/state_transition_selfdestruct_test.cpp new file mode 100644 index 0000000000..aa2253533c --- /dev/null +++ b/test/unittests/state_transition_selfdestruct_test.cpp @@ -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; +}