Skip to content

Commit

Permalink
statetest: Implement JSON test file loading
Browse files Browse the repository at this point in the history
  • Loading branch information
chfast committed Jul 6, 2022
1 parent 82e87ad commit b36fabf
Show file tree
Hide file tree
Showing 4 changed files with 261 additions and 6 deletions.
6 changes: 5 additions & 1 deletion test/statetest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
5 changes: 1 addition & 4 deletions test/statetest/statetest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@

#include "statetest.hpp"
#include <gtest/gtest.h>
#include <filesystem>
#include <iostream>

namespace fs = std::filesystem;

namespace
{
class StateTest : public testing::Test
Expand All @@ -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

Expand Down
58 changes: 57 additions & 1 deletion test/statetest/statetest.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,61 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once

#include "../state/state.hpp"
#include <filesystem>

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<state::AccessList> access_lists;
std::vector<bytes> inputs;
std::vector<int64_t> gas_limits;
std::vector<intx::uint256> 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<Case> cases;
};

struct StateTransitionTest
{
state::State pre_state;
state::BlockInfo block;
TestMultiTransaction multi_tx;
std::vector<TestCase> cases;
};

StateTransitionTest load_state_test(const fs::path& test_file);

} // namespace evmone::test
198 changes: 198 additions & 0 deletions test/statetest/statetest_loader.cpp
Original file line number Diff line number Diff line change
@@ -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 <nlohmann/json.hpp>
#include <fstream>

namespace evmone::test
{
namespace json = nlohmann;
using evmc::from_hex;

namespace
{
template <typename T>
T from_json(const json::json& j) = delete;

template <>
int64_t from_json<int64_t>(const json::json& j)
{
return static_cast<int64_t>(std::stoll(j.get<std::string>(), nullptr, 16));
}

template <>
uint64_t from_json<uint64_t>(const json::json& j)
{
return static_cast<uint64_t>(std::stoull(j.get<std::string>(), nullptr, 16));
}

template <>
bytes from_json<bytes>(const json::json& j)
{
return from_hex(j.get<std::string>()).value();
}

template <>
address from_json<address>(const json::json& j)
{
return evmc::from_hex<address>(j.get<std::string>()).value();
}

template <>
hash256 from_json<hash256>(const json::json& j)
{
return evmc::from_hex<hash256>(j.get<std::string>()).value();
}

template <>
intx::uint256 from_json<intx::uint256>(const json::json& j)
{
const auto s = j.get<std::string>();
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<intx::uint256>::max(); // Fake it
return intx::from_string<intx::uint256>(s);
}

template <>
state::AccessList from_json<state::AccessList>(const json::json& j)
{
state::AccessList o;
for (const auto& a : j)
{
o.push_back({from_json<address>(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<hash256>(storage_key));
}
return o;
}

template <>
state::BlockInfo from_json<state::BlockInfo>(const json::json& j)
{
const auto prev_randao_it = j.find("currentRandom");
return {
from_json<int64_t>(j.at("currentNumber")),
from_json<int64_t>(j.at("currentTimestamp")),
from_json<int64_t>(j.at("currentGasLimit")),
from_json<evmc::address>(j.at("currentCoinbase")),
from_json<evmc::bytes32>(
(prev_randao_it != j.end()) ? *prev_randao_it : j.at("currentDifficulty")),
from_json<uint64_t>(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<intx::uint256>(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<intx::uint256>(j.at("maxFeePerGas"));
o.max_priority_gas_price = from_json<intx::uint256>(j.at("maxPriorityFeePerGas"));
}
o.sender = from_json<evmc::address>(j.at("sender"));
if (!j.at("to").get<std::string>().empty())
o.to = from_json<evmc::address>(j["to"]);

for (const auto& j_data : j.at("data"))
o.inputs.emplace_back(from_json<bytes>(j_data));

if (j.contains("accessLists"))
{
for (const auto& j_access_list : j["accessLists"])
o.access_lists.emplace_back(from_json<state::AccessList>(j_access_list));
}

for (const auto& j_gas_limit : j.at("gasLimit"))
o.gas_limits.emplace_back(from_json<int64_t>(j_gas_limit));

for (const auto& j_value : j.at("value"))
o.values.emplace_back(from_json<intx::uint256>(j_value));
}

static void from_json(const json::json& j, TestMultiTransaction::Indexes& o)
{
o.input = j.at("data").get<size_t>();
o.gas_limit = j.at("gas").get<size_t>();
o.value = j.at("value").get<size_t>();
}

static void from_json(const json::json& j, TestCase::Case& o)
{
o.indexes = j.at("indexes").get<TestMultiTransaction::Indexes>();
o.state_hash = from_json<hash256>(j.at("hash"));
o.logs_hash = from_json<hash256>(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<address>(j_addr);
auto& acc = o.pre_state.create(addr);
acc.balance = from_json<intx::uint256>(j_acc.at("balance"));
acc.nonce = from_json<uint64_t>(j_acc.at("nonce"));
acc.code = from_json<bytes>(j_acc.at("code"));

for (const auto& [j_key, j_value] : j_acc.at("storage").items())
{
auto& slot = acc.storage[from_json<bytes32>(j_key)];
const auto value = from_json<bytes32>(j_value);
slot.original = value;
slot.current = value;
}
}

o.multi_tx = j_t.at("transaction").get<TestMultiTransaction>();

o.block = from_json<state::BlockInfo>(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<std::vector<TestCase::Case>>()});
}

StateTransitionTest load_state_test(const fs::path& test_file)
{
return json::json::parse(std::ifstream{test_file}).get<StateTransitionTest>();
}
} // namespace evmone::test

0 comments on commit b36fabf

Please sign in to comment.