diff --git a/test/statetest/statetest.hpp b/test/statetest/statetest.hpp index 29a4ecf1c9..f6808b4cff 100644 --- a/test/statetest/statetest.hpp +++ b/test/statetest/statetest.hpp @@ -68,6 +68,12 @@ struct StateTransitionTest template T from_json(const json::json& j) = delete; +template <> +uint64_t from_json(const json::json& j); + +template <> +int64_t from_json(const json::json& j); + template <> state::BlockInfo from_json(const json::json& j); diff --git a/test/statetest/statetest_loader.cpp b/test/statetest/statetest_loader.cpp index 83997c316d..bdcce19065 100644 --- a/test/statetest/statetest_loader.cpp +++ b/test/statetest/statetest_loader.cpp @@ -20,16 +20,44 @@ uint8_t from_json(const json::json& j) return static_cast(ret); } +template +static std::optional integer_from_json(const json::json& j) +{ + if (j.is_number_integer()) + return j.get(); + + if (!j.is_string()) + return {}; + + const auto s = j.get(); + size_t num_processed = 0; + T v = 0; + if constexpr (std::is_same_v) + v = std::stoull(s, &num_processed, 0); + else + v = std::stoll(s, &num_processed, 0); + + if (num_processed == 0 || num_processed != s.size()) + return {}; + return v; +} + template <> int64_t from_json(const json::json& j) { - return static_cast(std::stoll(j.get(), nullptr, 16)); + const auto v = integer_from_json(j); + if (!v.has_value()) + throw std::invalid_argument("from_json: must be integer or string of integer"); + return *v; } template <> uint64_t from_json(const json::json& j) { - return static_cast(std::stoull(j.get(), nullptr, 16)); + const auto v = integer_from_json(j); + if (!v.has_value()) + throw std::invalid_argument("from_json: must be integer or string of integer"); + return *v; } template <> @@ -120,7 +148,14 @@ state::BlockInfo from_json(const json::json& j) const auto current_difficulty_it = j.find("currentDifficulty"); const auto parent_difficulty_it = j.find("parentDifficulty"); if (prev_randao_it != j.end()) - difficulty = from_json(*prev_randao_it); + { + // Special case to handle "0". Required by exec-spec-tests. + // TODO: Get rid of it. + if (prev_randao_it->is_string() && prev_randao_it->get() == "0") + difficulty = 0x0000000000000000000000000000000000000000000000000000000000000000_bytes32; + else + difficulty = from_json(*prev_randao_it); + } else if (current_difficulty_it != j.end()) difficulty = from_json(*current_difficulty_it); else if (parent_difficulty_it != j.end()) diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index 9f9d5d62e0..24461911d1 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -36,6 +36,7 @@ target_sources( state_mpt_test.cpp state_new_account_address_test.cpp state_rlp_test.cpp + statetest_loader_block_info_test.cpp statetest_loader_test.cpp statetest_loader_tx_test.cpp statetest_logs_hash_test.cpp diff --git a/test/unittests/statetest_loader_block_info_test.cpp b/test/unittests/statetest_loader_block_info_test.cpp new file mode 100644 index 0000000000..5003958cfe --- /dev/null +++ b/test/unittests/statetest_loader_block_info_test.cpp @@ -0,0 +1,114 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2023 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +using namespace evmone; + +TEST(statetest_loader, block_info) +{ + constexpr std::string_view input = R"({ + "currentCoinbase": "0x1111111111111111111111111111111111111111", + "currentDifficulty": "0x0", + "currentGasLimit": "0x0", + "currentNumber": "0", + "currentTimestamp": "0", + "currentBaseFee": "7", + "currentRandom": "0x00", + "withdrawals": [] + })"; + + const auto bi = test::from_json(json::json::parse(input)); + EXPECT_EQ(bi.coinbase, 0x1111111111111111111111111111111111111111_address); + EXPECT_EQ(bi.prev_randao, 0x00_bytes32); + EXPECT_EQ(bi.gas_limit, 0x0); + EXPECT_EQ(bi.base_fee, 7); + EXPECT_EQ(bi.timestamp, 0); + EXPECT_EQ(bi.number, 0); +} + +TEST(statetest_loader, block_info_hex) +{ + constexpr std::string_view input = R"({ + "currentCoinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentGasLimit": "0x16345785D8A0000", + "currentNumber": "1", + "currentTimestamp": "0x3E8", + "currentRandom": "0x00", + "currentDifficulty": "1", + "parentDifficulty": "0", + "parentBaseFee": "7", + "parentGasUsed": "0", + "parentGasLimit": "0x16345785D8A0000", + "parentTimstamp": "0", + "blockHashes": { + "0": "0xc305d826e3784046a7e9d31128ef98d3e96133fe454c16ef630574d967dfdb1a" + }, + "ommers": [], + "withdrawals": [], + "parentUncleHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + })"; + + const auto bi = test::from_json(json::json::parse(input)); + EXPECT_EQ(bi.coinbase, 0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba_address); + EXPECT_EQ(bi.prev_randao, 0x00_bytes32); + EXPECT_EQ(bi.gas_limit, 100000000000000000); + EXPECT_EQ(bi.base_fee, 7); + EXPECT_EQ(bi.timestamp, 1000); + EXPECT_EQ(bi.number, 1); +} + +TEST(statetest_loader, block_info_dec) +{ + constexpr std::string_view input = R"({ + "currentCoinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentGasLimit": "100000000000000000", + "currentNumber": "1", + "currentTimestamp": "1000", + "currentRandom": "0x00", + "currentDifficulty": "0", + "parentDifficulty": "0", + "parentBaseFee": "7", + "parentGasUsed": "0", + "parentGasLimit": "100000000000000000", + "parentTimstamp": "0", + "blockHashes": { + "0": "0xc305d826e3784046a7e9d31128ef98d3e96133fe454c16ef630574d967dfdb1a" + }, + "ommers": [], + "withdrawals": [], + "parentUncleHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + })"; + + const auto bi = test::from_json(json::json::parse(input)); + EXPECT_EQ(bi.coinbase, 0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba_address); + EXPECT_EQ(bi.prev_randao, 0x00_bytes32); + EXPECT_EQ(bi.gas_limit, 100000000000000000); + EXPECT_EQ(bi.base_fee, 7); + EXPECT_EQ(bi.timestamp, 1000); + EXPECT_EQ(bi.number, 1); +} + +TEST(statetest_loader, block_info_0_random) +{ + constexpr std::string_view input = R"({ + "currentCoinbase": "0x1111111111111111111111111111111111111111", + "currentDifficulty": "0x0", + "currentGasLimit": "0x0", + "currentNumber": "0", + "currentTimestamp": "0", + "currentBaseFee": "7", + "currentRandom": "0", + "withdrawals": [] + })"; + + const auto bi = test::from_json(json::json::parse(input)); + EXPECT_EQ(bi.coinbase, 0x1111111111111111111111111111111111111111_address); + EXPECT_EQ(bi.prev_randao, 0x00_bytes32); + EXPECT_EQ(bi.gas_limit, 0x0); + EXPECT_EQ(bi.base_fee, 7); + EXPECT_EQ(bi.timestamp, 0); + EXPECT_EQ(bi.number, 0); +} diff --git a/test/unittests/statetest_loader_test.cpp b/test/unittests/statetest_loader_test.cpp index 856cde1d85..866c584800 100644 --- a/test/unittests/statetest_loader_test.cpp +++ b/test/unittests/statetest_loader_test.cpp @@ -6,11 +6,66 @@ #include using namespace evmone; +using namespace evmone::test; + +TEST(json_loader, uint64_t) +{ + using json::basic_json; + + EXPECT_EQ(from_json(basic_json("0x00000005")), 5); + EXPECT_EQ(from_json(basic_json("5")), 5); + EXPECT_EQ(from_json(basic_json(7)), 7); + + EXPECT_EQ(from_json(basic_json("0xffffffffffffffff")), + std::numeric_limits::max()); + EXPECT_EQ(from_json(basic_json("18446744073709551615")), + std::numeric_limits::max()); + EXPECT_THROW(from_json(basic_json("0x10000000000000000")), std::out_of_range); + EXPECT_THROW(from_json(basic_json("18446744073709551616")), std::out_of_range); + EXPECT_EQ(from_json(basic_json(std::numeric_limits::max())), + std::numeric_limits::max()); + + // Octal is also supported. + EXPECT_EQ(from_json(basic_json("0777")), 0777); + + EXPECT_THROW(from_json(basic_json("0x000000000000000k")), std::invalid_argument); + EXPECT_THROW(from_json(basic_json("k")), std::invalid_argument); + EXPECT_THROW(from_json(basic_json("")), std::invalid_argument); +} + +TEST(json_loader, int64_t) +{ + using json::basic_json; + + EXPECT_EQ(from_json(basic_json("0x00000005")), 5); + EXPECT_EQ(from_json(basic_json("-0x5")), -5); + EXPECT_EQ(from_json(basic_json("-5")), -5); + + EXPECT_EQ(from_json(basic_json(-7)), -7); + EXPECT_EQ(from_json(basic_json(0xffffffffffffffff)), -1); + + EXPECT_EQ( + from_json(basic_json("0x7fffffffffffffff")), std::numeric_limits::max()); + EXPECT_EQ( + from_json(basic_json("9223372036854775807")), std::numeric_limits::max()); + EXPECT_EQ(from_json(basic_json("-9223372036854775808")), + std::numeric_limits::min()); + EXPECT_THROW(from_json(basic_json("0xffffffffffffffff")), std::out_of_range); + EXPECT_THROW(from_json(basic_json("9223372036854775808")), std::out_of_range); + EXPECT_THROW(from_json(basic_json("-9223372036854775809")), std::out_of_range); + + // Octal is also supported. + EXPECT_EQ(from_json(basic_json("0777")), 0777); + + EXPECT_THROW(from_json(basic_json("0x000000000000000k")), std::invalid_argument); + EXPECT_THROW(from_json(basic_json("k")), std::invalid_argument); + EXPECT_THROW(from_json(basic_json("")), std::invalid_argument); +} TEST(statetest_loader, load_empty_test) { std::istringstream s{"{}"}; - EXPECT_THROW(test::load_state_test(s), std::invalid_argument); + EXPECT_THROW(load_state_test(s), std::invalid_argument); } TEST(statetest_loader, load_minimal_test) @@ -36,7 +91,7 @@ TEST(statetest_loader, load_minimal_test) } } })"}; - const test::StateTransitionTest st = test::load_state_test(s); + const StateTransitionTest st = load_state_test(s); // TODO: should add some comparison operator to State, BlockInfo, AccessList EXPECT_EQ(st.pre_state.get_accounts().size(), 0); EXPECT_EQ(st.block.number, 0); diff --git a/test/unittests/statetest_loader_tx_test.cpp b/test/unittests/statetest_loader_tx_test.cpp index fdad282f6f..07a1c50526 100644 --- a/test/unittests/statetest_loader_tx_test.cpp +++ b/test/unittests/statetest_loader_tx_test.cpp @@ -15,7 +15,7 @@ TEST(statetest_loader, tx_create_legacy) { constexpr std::string_view input = R"({ "input": "b0b1", - "gas": "9091", + "gas": "0x9091", "value": "0xe0e1", "sender": "a0a1", "to": "", @@ -46,7 +46,7 @@ TEST(statetest_loader, tx_eip1559) { constexpr std::string_view input = R"({ "input": "b0b1", - "gas": "9091", + "gas": "0x9091", "value": "0xe0e1", "sender": "a0a1", "to": "c0c1",