From 6d8a14de9850bd3ba17433f3b5e063292dd36a93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Wed, 22 Nov 2023 23:11:32 +0100 Subject: [PATCH] Refactor precompiles detection Extract a precompile address recognition into new is_precompile() function and use it in all places where this information is needed. Improve maintenance of "precompile ids" by adding aliases to the enum. Add unit tests for is_precompile(). --- test/state/host.cpp | 6 ++-- test/state/precompiles.cpp | 30 +++++++++------- test/state/precompiles.hpp | 17 ++++++--- test/unittests/CMakeLists.txt | 1 + test/unittests/state_precompiles_test.cpp | 42 +++++++++++++++++++++++ 5 files changed, 77 insertions(+), 19 deletions(-) create mode 100644 test/unittests/state_precompiles_test.cpp diff --git a/test/state/host.cpp b/test/state/host.cpp index 3df784a81a..4741757aad 100644 --- a/test/state/host.cpp +++ b/test/state/host.cpp @@ -263,8 +263,8 @@ evmc::Result Host::execute_message(const evmc_message& msg) noexcept dst_acc->balance += value; } - if (auto precompiled_result = call_precompile(m_rev, msg); precompiled_result.has_value()) - return std::move(*precompiled_result); + if (is_precompile(m_rev, msg.code_address)) + return call_precompile(m_rev, msg); // Copy of the code. Revert will invalidate the account. const auto code = dst_acc != nullptr ? dst_acc->code : bytes{}; @@ -350,7 +350,7 @@ evmc_access_status Host::access_account(const address& addr) noexcept const auto status = std::exchange(acc.access_status, EVMC_ACCESS_WARM); // Overwrite status for precompiled contracts: they are always warm. - if (status == EVMC_ACCESS_COLD && addr >= 0x01_address && addr <= 0x09_address) + if (status == EVMC_ACCESS_COLD && is_precompile(m_rev, addr)) return EVMC_ACCESS_WARM; return status; diff --git a/test/state/precompiles.cpp b/test/state/precompiles.cpp index 8200b14f39..adbe376662 100644 --- a/test/state/precompiles.cpp +++ b/test/state/precompiles.cpp @@ -278,25 +278,31 @@ inline constexpr auto traits = []() noexcept { }(); } // namespace -std::optional call_precompile(evmc_revision rev, const evmc_message& msg) noexcept +bool is_precompile(evmc_revision rev, const evmc::address& addr) noexcept { // Define compile-time constant, - // TODO: workaround for Clang Analyzer bug https://github.com/llvm/llvm-project/issues/59493. - static constexpr evmc::address address_boundary{NumPrecompiles}; + // TODO(clang18): workaround for Clang Analyzer bug, fixed in clang 18. + // https://github.com/llvm/llvm-project/issues/59493. + static constexpr evmc::address address_boundary{stdx::to_underlying(PrecompileId::latest)}; - if (evmc::is_zero(msg.code_address) || msg.code_address >= address_boundary) - return {}; + if (evmc::is_zero(addr) || addr > address_boundary) + return false; - const auto id = msg.code_address.bytes[19]; - if (rev < EVMC_BYZANTIUM && id > 4) - return {}; + const auto id = addr.bytes[19]; + if (rev < EVMC_BYZANTIUM && id >= stdx::to_underlying(PrecompileId::since_byzantium)) + return false; + + if (rev < EVMC_ISTANBUL && id >= stdx::to_underlying(PrecompileId::since_istanbul)) + return false; - if (rev < EVMC_ISTANBUL && id > 8) - return {}; + return true; +} - assert(id > 0); +evmc::Result call_precompile(evmc_revision rev, const evmc_message& msg) noexcept +{ assert(msg.gas >= 0); + const auto id = msg.code_address.bytes[19]; const auto [analyze, execute] = traits[id]; const bytes_view input{msg.input_data, msg.input_size}; @@ -307,7 +313,7 @@ std::optional call_precompile(evmc_revision rev, const evmc_messag static Cache cache; if (auto r = cache.find(static_cast(id), input, gas_left); r.has_value()) - return r; + return std::move(*r); // Buffer for the precompile's output. // Big enough to handle all "expmod" tests, but in case does not match the size requirement diff --git a/test/state/precompiles.hpp b/test/state/precompiles.hpp index 79b861d693..f507d57b76 100644 --- a/test/state/precompiles.hpp +++ b/test/state/precompiles.hpp @@ -9,9 +9,7 @@ namespace evmone::state { -/// The total number of known precompiles ids, including 0. -inline constexpr std::size_t NumPrecompiles = 10; - +/// The precompiles identifies and their corresponding addresses. enum class PrecompileId : uint8_t { ecrecover = 0x01, @@ -23,13 +21,24 @@ enum class PrecompileId : uint8_t ecmul = 0x07, ecpairing = 0x08, blake2bf = 0x09, + + since_byzantium = expmod, ///< The first precompile introduced in Byzantium. + since_istanbul = blake2bf, ///< The first precompile introduced in Istanbul. + latest = blake2bf ///< The latest introduced precompile (highest address). }; +/// The total number of known precompiles ids, including 0. +inline constexpr std::size_t NumPrecompiles = stdx::to_underlying(PrecompileId::latest) + 1; + struct ExecutionResult { evmc_status_code status_code; size_t output_size; }; -std::optional call_precompile(evmc_revision rev, const evmc_message& msg) noexcept; +/// Checks if the address @p addr is considered a precompiled contract in the revision @p rev. +bool is_precompile(evmc_revision rev, const evmc::address& addr) noexcept; + +/// Executes the message to a precompiled contract (msg.code_address must be a precompile). +evmc::Result call_precompile(evmc_revision rev, const evmc_message& msg) noexcept; } // namespace evmone::state diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index 517b4b6391..a08098fa96 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -44,6 +44,7 @@ target_sources( state_mpt_hash_test.cpp state_mpt_test.cpp state_new_account_address_test.cpp + state_precompiles_test.cpp state_rlp_test.cpp state_transition.hpp state_transition.cpp diff --git a/test/unittests/state_precompiles_test.cpp b/test/unittests/state_precompiles_test.cpp new file mode 100644 index 0000000000..fb80da76f9 --- /dev/null +++ b/test/unittests/state_precompiles_test.cpp @@ -0,0 +1,42 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2023 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +using namespace evmc; +using namespace evmone::state; + +TEST(state_precompiles, is_precompile) +{ + for (int r = 0; r <= EVMC_MAX_REVISION; ++r) + { + const auto rev = static_cast(r); + + EXPECT_FALSE(is_precompile(rev, 0x00_address)); + + // Frontier: + EXPECT_TRUE(is_precompile(rev, 0x01_address)); + EXPECT_TRUE(is_precompile(rev, 0x02_address)); + EXPECT_TRUE(is_precompile(rev, 0x03_address)); + EXPECT_TRUE(is_precompile(rev, 0x04_address)); + + // Byzantium: + EXPECT_EQ(is_precompile(rev, 0x05_address), rev >= EVMC_BYZANTIUM); + EXPECT_EQ(is_precompile(rev, 0x06_address), rev >= EVMC_BYZANTIUM); + EXPECT_EQ(is_precompile(rev, 0x07_address), rev >= EVMC_BYZANTIUM); + EXPECT_EQ(is_precompile(rev, 0x08_address), rev >= EVMC_BYZANTIUM); + + // Istanbul: + EXPECT_EQ(is_precompile(rev, 0x09_address), rev >= EVMC_ISTANBUL); + + // Future? + EXPECT_FALSE(is_precompile(rev, 0x0a_address)); + EXPECT_FALSE(is_precompile(rev, 0x0b_address)); + EXPECT_FALSE(is_precompile(rev, 0x0c_address)); + EXPECT_FALSE(is_precompile(rev, 0x0d_address)); + EXPECT_FALSE(is_precompile(rev, 0x0e_address)); + EXPECT_FALSE(is_precompile(rev, 0x0f_address)); + } +}