diff --git a/test/statetest/statetest_loader.cpp b/test/statetest/statetest_loader.cpp index 9839d1b5e7..4a9106014e 100644 --- a/test/statetest/statetest_loader.cpp +++ b/test/statetest/statetest_loader.cpp @@ -182,13 +182,24 @@ evmc_revision to_rev(std::string_view s) throw std::invalid_argument{"unknown revision: " + std::string{s}}; } -static void from_json(const json::json& j, TestMultiTransaction& o) +/// Load common parts of Transaction or TestMultiTransaction. +static void from_json_tx_common(const json::json& j, state::Transaction& o) { - if (j.contains("gasPrice")) + o.sender = from_json(j.at("sender")); + + if (const auto& to = j.at("to"); !to.get().empty()) + o.to = from_json(to); + + if (const auto gas_price_it = j.find("gasPrice"); gas_price_it != j.end()) { o.kind = state::Transaction::Kind::legacy; - o.max_gas_price = from_json(j.at("gasPrice")); + o.max_gas_price = from_json(*gas_price_it); o.max_priority_gas_price = o.max_gas_price; + if (j.contains("maxFeePerGas") || j.contains("maxPriorityFeePerGas")) + { + throw std::invalid_argument( + "Misformatted transaction -- contains both legacy and 1559 fees"); + } } else { @@ -196,9 +207,26 @@ static void from_json(const json::json& j, TestMultiTransaction& o) 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"]); +} + +template <> +state::Transaction from_json(const json::json& j) +{ + state::Transaction o; + from_json_tx_common(j, o); + o.data = from_json(j.at("input")); + o.gas_limit = from_json(j.at("gas")); + o.value = from_json(j.at("value")); + + if (const auto ac_it = j.find("accessList"); ac_it != j.end()) + o.access_list = from_json(*ac_it); + + return o; +} + +static void from_json(const json::json& j, TestMultiTransaction& o) +{ + from_json_tx_common(j, o); for (const auto& j_data : j.at("data")) o.inputs.emplace_back(from_json(j_data)); @@ -255,37 +283,6 @@ static void from_json(const json::json& j, StateTransitionTest& o) } } -template <> -state::Transaction from_json(const json::json& j) -{ - state::Transaction o; - o.data = from_json(j.at("input")); - o.gas_limit = from_json(j.at("gas")); - o.value = from_json(j.at("value")); - o.sender = from_json(j.at("sender")); - - if (!j.at("to").get().empty()) - o.to = from_json(j.at("to")); - - 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")); - } - - if (j.contains("accessList")) - o.access_list = from_json(j.at("accessList")); - - return o; -} - StateTransitionTest load_state_test(const fs::path& test_file) { return json::json::parse(std::ifstream{test_file}).get(); diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index b0117fdf66..42c102089d 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -30,6 +30,7 @@ add_executable(evmone-unittests state_mpt_hash_test.cpp state_mpt_test.cpp state_rlp_test.cpp + statetest_loader_tx_test.cpp statetest_logs_hash_test.cpp tracing_test.cpp utils_test.cpp diff --git a/test/unittests/statetest_loader_tx_test.cpp b/test/unittests/statetest_loader_tx_test.cpp new file mode 100644 index 0000000000..f94f939026 --- /dev/null +++ b/test/unittests/statetest_loader_tx_test.cpp @@ -0,0 +1,106 @@ +// 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, tx_create_legacy) +{ + constexpr std::string_view input = R"({ + "input": "b0b1", + "gas": "9091", + "value": "0xe0e1", + "sender": "a0a1", + "to": "", + "gasPrice": "0x7071" + })"; + + const auto tx = test::from_json(json::json::parse(input)); + EXPECT_EQ(tx.kind, state::Transaction::Kind::legacy); + EXPECT_EQ(tx.data, (bytes{0xb0, 0xb1})); + EXPECT_EQ(tx.gas_limit, 0x9091); + EXPECT_EQ(tx.value, 0xe0e1); + EXPECT_EQ(tx.sender, 0xa0a1_address); + EXPECT_FALSE(tx.to.has_value()); + EXPECT_EQ(tx.max_gas_price, 0x7071); + EXPECT_EQ(tx.max_priority_gas_price, 0x7071); + EXPECT_TRUE(tx.access_list.empty()); +} + +TEST(statetest_loader, tx_eip1559) +{ + constexpr std::string_view input = R"({ + "input": "b0b1", + "gas": "9091", + "value": "0xe0e1", + "sender": "a0a1", + "to": "c0c1", + "maxFeePerGas": "0x7071", + "maxPriorityFeePerGas": "0x6061", + "accessList": [] + })"; + + const auto tx = test::from_json(json::json::parse(input)); + EXPECT_EQ(tx.kind, state::Transaction::Kind::eip1559); + EXPECT_EQ(tx.data, (bytes{0xb0, 0xb1})); + EXPECT_EQ(tx.gas_limit, 0x9091); + EXPECT_EQ(tx.value, 0xe0e1); + EXPECT_EQ(tx.sender, 0xa0a1_address); + EXPECT_EQ(tx.to, 0xc0c1_address); + EXPECT_EQ(tx.max_gas_price, 0x7071); + EXPECT_EQ(tx.max_priority_gas_price, 0x6061); + EXPECT_TRUE(tx.access_list.empty()); +} + +TEST(statetest_loader, tx_access_list) +{ + constexpr std::string_view input = R"({ + "input": "", + "gas": "0", + "value": "0", + "sender": "", + "to": "", + "maxFeePerGas": "0", + "maxPriorityFeePerGas": "0", + "accessList": [ + {"address": "ac01", "storageKeys": []}, + {"address": "ac02", "storageKeys": ["fe", "00"]} + ] + })"; + + const auto tx = test::from_json(json::json::parse(input)); + EXPECT_EQ(tx.kind, state::Transaction::Kind::eip1559); + EXPECT_TRUE(tx.data.empty()); + EXPECT_EQ(tx.gas_limit, 0); + EXPECT_EQ(tx.value, 0); + EXPECT_EQ(tx.sender, address{}); // TODO: use 0x0_address? + EXPECT_FALSE(tx.to.has_value()); + EXPECT_EQ(tx.max_gas_price, 0); + EXPECT_EQ(tx.max_priority_gas_price, 0); + ASSERT_EQ(tx.access_list.size(), 2); + EXPECT_EQ(tx.access_list[0].first, 0xac01_address); + EXPECT_EQ(tx.access_list[0].second.size(), 0); + EXPECT_EQ(tx.access_list[1].first, 0xac02_address); + EXPECT_EQ(tx.access_list[1].second, (std::vector{0xfe_bytes32, 0x00_bytes32})); +} + +TEST(statetest_loader, tx_confusing) +{ + constexpr std::string_view input = R"({ + "input": "b0b1", + "gas": "9091", + "value": "0xe0e1", + "sender": "a0a1", + "to": "c0c1", + "gasPrice": "0x8081", + "maxFeePerGas": "0x7071", + "maxPriorityFeePerGas": "0x6061", + "accessList": [] + })"; + + EXPECT_THROW( + test::from_json(json::json::parse(input)), std::invalid_argument); +}