From 9ec1223b107c5ea91923c30776beda3f86732e99 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 | 192 ++++++++++++++++++++++++++++ 4 files changed, 255 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 cf65b3a4c1..19d095bd74 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..ac895942d4 --- /dev/null +++ b/test/statetest/statetest_loader.cpp @@ -0,0 +1,192 @@ +// 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; +} + +constexpr 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; + 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["gasPrice"]); + o.max_priority_gas_price = o.max_gas_price; + } + else + { + o.kind = state::Transaction::Kind::eip1559; + o.max_gas_price = from_json(j["maxFeePerGas"]); + o.max_priority_gas_price = from_json(j["maxPriorityFeePerGas"]); + } + o.nonce = from_json(j["nonce"]); + o.sender = from_json(j["sender"]); + if (!j["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["data"].get(); + o.gas_limit = j["gas"].get(); + o.value = j["value"].get(); +} + +static void from_json(const json::json& j, TestCase::Case& o) +{ + o.indexes = j["indexes"].get(); + o.state_hash = from_json(j["hash"]); + o.logs_hash = from_json(j["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["pre"].items()) + { + const auto addr = from_json
(j_addr); + auto& acc = o.pre_state.create(addr); + acc.balance = from_json(j_acc["balance"]); + acc.nonce = from_json(j_acc["nonce"]); + acc.code = from_json(j_acc["code"]); + + for (const auto& [j_key, j_value] : j_acc["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["transaction"].get(); + + const auto& env = j_t["env"]; + o.block.gas_limit = from_json(env["currentGasLimit"]); + o.block.coinbase = from_json(env["currentCoinbase"]); + o.block.base_fee = from_json(env["currentBaseFee"]); + o.block.difficulty = from_json(env["currentDifficulty"]); + o.block.number = from_json(env["currentNumber"]); + o.block.timestamp = from_json(env["currentTimestamp"]); + + // TODO: Chain ID is expected to be 1. + o.block.chain_id = {}; + o.block.chain_id.bytes[31] = 1; + + for (const auto& [rev_name, posts] : j_t["post"].items()) + o.cases.push_back({to_rev(rev_name), posts.get>()}); +} + +StateTransitionTest load_state_test(const fs::path& test_file) +{ + return json::json::parse(std::ifstream{test_file}).get(); +} +} // namespace evmone::test