From b36fabfa305c55ca681f72bbba5c0b076c8829ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Wed, 18 May 2022 15:37:43 +0200 Subject: [PATCH] statetest: Implement JSON test file loading --- test/statetest/CMakeLists.txt | 6 +- test/statetest/statetest.cpp | 5 +- test/statetest/statetest.hpp | 58 +++++++- test/statetest/statetest_loader.cpp | 198 ++++++++++++++++++++++++++++ 4 files changed, 261 insertions(+), 6 deletions(-) create mode 100644 test/statetest/statetest_loader.cpp diff --git a/test/statetest/CMakeLists.txt b/test/statetest/CMakeLists.txt index 2740d24a0f..20e9511e40 100644 --- a/test/statetest/CMakeLists.txt +++ b/test/statetest/CMakeLists.txt @@ -2,10 +2,14 @@ # Copyright 2022 The evmone Authors. # SPDX-License-Identifier: Apache-2.0 +hunter_add_package(nlohmann_json) +find_package(nlohmann_json CONFIG REQUIRED) + add_executable(evmone-statetest) -target_link_libraries(evmone-statetest PRIVATE evmone evmone::state GTest::gtest) +target_link_libraries(evmone-statetest PRIVATE evmone evmone::state nlohmann_json::nlohmann_json GTest::gtest) target_sources( evmone-statetest PRIVATE statetest.hpp statetest.cpp + statetest_loader.cpp ) diff --git a/test/statetest/statetest.cpp b/test/statetest/statetest.cpp index b5fb8ed546..9ef65537f0 100644 --- a/test/statetest/statetest.cpp +++ b/test/statetest/statetest.cpp @@ -4,11 +4,8 @@ #include "statetest.hpp" #include -#include #include -namespace fs = std::filesystem; - namespace { class StateTest : public testing::Test @@ -20,7 +17,7 @@ class StateTest : public testing::Test : m_json_test_file{std::move(json_test_file)} {} - void TestBody() final { std::cout << m_json_test_file << "\n"; } + void TestBody() final { evmone::test::load_state_test(m_json_test_file); } }; } // namespace diff --git a/test/statetest/statetest.hpp b/test/statetest/statetest.hpp index 5d9c058c3a..41c4171589 100644 --- a/test/statetest/statetest.hpp +++ b/test/statetest/statetest.hpp @@ -3,5 +3,61 @@ // SPDX-License-Identifier: Apache-2.0 #pragma once +#include "../state/state.hpp" +#include + +namespace fs = std::filesystem; + namespace evmone::test -{} +{ +struct TestMultiTransaction : state::Transaction +{ + struct Indexes + { + size_t input; + size_t gas_limit; + size_t value; + }; + + std::vector access_lists; + std::vector inputs; + std::vector gas_limits; + std::vector values; + + [[nodiscard]] Transaction get(const Indexes& indexes) const noexcept + { + Transaction tx{*this}; + if (!access_lists.empty()) + tx.access_list = access_lists.at(indexes.input); + tx.data = inputs.at(indexes.input); + tx.gas_limit = gas_limits.at(indexes.gas_limit); + tx.value = values.at(indexes.value); + return tx; + } +}; + +struct TestCase +{ + struct Case + { + TestMultiTransaction::Indexes indexes; + hash256 state_hash; + hash256 logs_hash; + bool exception; + }; + + evmc_revision rev; + std::vector cases; +}; + +struct StateTransitionTest +{ + state::State pre_state; + state::BlockInfo block; + TestMultiTransaction multi_tx; + std::vector cases; +}; + +StateTransitionTest load_state_test(const fs::path& test_file); + +} // namespace evmone::test diff --git a/test/statetest/statetest_loader.cpp b/test/statetest/statetest_loader.cpp new file mode 100644 index 0000000000..440f041557 --- /dev/null +++ b/test/statetest/statetest_loader.cpp @@ -0,0 +1,198 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2022 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include "statetest.hpp" +#include +#include + +namespace evmone::test +{ +namespace json = nlohmann; +using evmc::from_hex; + +namespace +{ +template +T from_json(const json::json& j) = delete; + +template <> +int64_t from_json(const json::json& j) +{ + return static_cast(std::stoll(j.get(), nullptr, 16)); +} + +template <> +uint64_t from_json(const json::json& j) +{ + return static_cast(std::stoull(j.get(), nullptr, 16)); +} + +template <> +bytes from_json(const json::json& j) +{ + return from_hex(j.get()).value(); +} + +template <> +address from_json
(const json::json& j) +{ + return evmc::from_hex
(j.get()).value(); +} + +template <> +hash256 from_json(const json::json& j) +{ + return evmc::from_hex(j.get()).value(); +} + +template <> +intx::uint256 from_json(const json::json& j) +{ + const auto s = j.get(); + static constexpr std::string_view bigint_marker{"0x:bigint "}; + if (std::string_view{s}.substr(0, bigint_marker.size()) == bigint_marker) + return std::numeric_limits::max(); // Fake it + return intx::from_string(s); +} + +template <> +state::AccessList from_json(const json::json& j) +{ + state::AccessList o; + for (const auto& a : j) + { + o.push_back({from_json
(a.at("address")), {}}); + auto& storage_access_list = o.back().second; + for (const auto& storage_key : a.at("storageKeys")) + storage_access_list.emplace_back(from_json(storage_key)); + } + return o; +} + +template <> +state::BlockInfo from_json(const json::json& j) +{ + const auto prev_randao_it = j.find("currentRandom"); + return { + from_json(j.at("currentNumber")), + from_json(j.at("currentTimestamp")), + from_json(j.at("currentGasLimit")), + from_json(j.at("currentCoinbase")), + from_json( + (prev_randao_it != j.end()) ? *prev_randao_it : j.at("currentDifficulty")), + from_json(j.value("currentBaseFee", std::string{"0"})), + }; +} + +evmc_revision to_rev(std::string_view s) +{ + if (s == "Frontier") + return EVMC_FRONTIER; + if (s == "Homestead") + return EVMC_HOMESTEAD; + if (s == "EIP150") + return EVMC_TANGERINE_WHISTLE; + if (s == "EIP158") + return EVMC_SPURIOUS_DRAGON; + if (s == "Byzantium") + return EVMC_BYZANTIUM; + if (s == "Constantinople") + return EVMC_CONSTANTINOPLE; + if (s == "ConstantinopleFix") + return EVMC_PETERSBURG; + if (s == "Istanbul") + return EVMC_ISTANBUL; + if (s == "Berlin") + return EVMC_BERLIN; + if (s == "London") + return EVMC_LONDON; + if (s == "Merge") + return EVMC_PARIS; + throw std::invalid_argument{"unknown revision: " + std::string{s}}; +} +} // namespace + +static void from_json(const json::json& j, TestMultiTransaction& o) +{ + if (j.contains("gasPrice")) + { + o.kind = state::Transaction::Kind::legacy; + o.max_gas_price = from_json(j.at("gasPrice")); + o.max_priority_gas_price = o.max_gas_price; + } + else + { + o.kind = state::Transaction::Kind::eip1559; + o.max_gas_price = from_json(j.at("maxFeePerGas")); + o.max_priority_gas_price = from_json(j.at("maxPriorityFeePerGas")); + } + o.sender = from_json(j.at("sender")); + if (!j.at("to").get().empty()) + o.to = from_json(j["to"]); + + for (const auto& j_data : j.at("data")) + o.inputs.emplace_back(from_json(j_data)); + + if (j.contains("accessLists")) + { + for (const auto& j_access_list : j["accessLists"]) + o.access_lists.emplace_back(from_json(j_access_list)); + } + + for (const auto& j_gas_limit : j.at("gasLimit")) + o.gas_limits.emplace_back(from_json(j_gas_limit)); + + for (const auto& j_value : j.at("value")) + o.values.emplace_back(from_json(j_value)); +} + +static void from_json(const json::json& j, TestMultiTransaction::Indexes& o) +{ + o.input = j.at("data").get(); + o.gas_limit = j.at("gas").get(); + o.value = j.at("value").get(); +} + +static void from_json(const json::json& j, TestCase::Case& o) +{ + o.indexes = j.at("indexes").get(); + o.state_hash = from_json(j.at("hash")); + o.logs_hash = from_json(j.at("logs")); + o.exception = j.contains("expectException"); +} + +static void from_json(const json::json& j, StateTransitionTest& o) +{ + const auto& j_t = j.begin().value(); // Content is in a dict with the test name. + + for (const auto& [j_addr, j_acc] : j_t.at("pre").items()) + { + const auto addr = from_json
(j_addr); + auto& acc = o.pre_state.create(addr); + acc.balance = from_json(j_acc.at("balance")); + acc.nonce = from_json(j_acc.at("nonce")); + acc.code = from_json(j_acc.at("code")); + + for (const auto& [j_key, j_value] : j_acc.at("storage").items()) + { + auto& slot = acc.storage[from_json(j_key)]; + const auto value = from_json(j_value); + slot.original = value; + slot.current = value; + } + } + + o.multi_tx = j_t.at("transaction").get(); + + o.block = from_json(j_t.at("env")); + + for (const auto& [rev_name, cases] : j_t.at("post").items()) + o.cases.push_back({to_rev(rev_name), cases.get>()}); +} + +StateTransitionTest load_state_test(const fs::path& test_file) +{ + return json::json::parse(std::ifstream{test_file}).get(); +} +} // namespace evmone::test