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;