From d2ecff0eb119ae2b00524e36165953926cdaaed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 23 Nov 2023 14:29:39 +0100 Subject: [PATCH] state: Implement EIP-6780 "SELFDESTRUCT" 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. --- test/state/account.hpp | 3 +++ test/state/host.cpp | 27 ++++++++++++++++++++++----- test/state/state.cpp | 5 ++++- 3 files changed, 29 insertions(+), 6 deletions(-) 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..ef766da9cd 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 calling SELFDESTRUCT was created" + acc.balance = 0; + beneficiary_acc.balance += balance; + + // 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 (this may be 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;