From 50c2108269da05677fbddfea4d086552d75d245d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Wed, 8 Feb 2023 12:14:44 +0100 Subject: [PATCH 1/4] test: Add transaction JSON loading unit tests Co-authored-by: Alex Beregszaszi --- test/unittests/CMakeLists.txt | 1 + test/unittests/statetest_loader_tx_test.cpp | 114 ++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 test/unittests/statetest_loader_tx_test.cpp 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..5b011c3856 --- /dev/null +++ b/test/unittests/statetest_loader_tx_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, 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": [] + })"; + + 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_EQ(tx.to, 0xc0c1_address); + EXPECT_EQ(tx.max_gas_price, 0x8081); + EXPECT_EQ(tx.max_priority_gas_price, 0x8081); + EXPECT_TRUE(tx.access_list.empty()); +} From f43158f3fc923bfcd3b70d98db6bad7a2c558f72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Wed, 8 Feb 2023 12:16:46 +0100 Subject: [PATCH 2/4] statetest: Move from_json implementation --- test/statetest/statetest_loader.cpp | 62 ++++++++++++++--------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/test/statetest/statetest_loader.cpp b/test/statetest/statetest_loader.cpp index 9839d1b5e7..1e1688b6f1 100644 --- a/test/statetest/statetest_loader.cpp +++ b/test/statetest/statetest_loader.cpp @@ -182,6 +182,37 @@ evmc_revision to_rev(std::string_view s) throw std::invalid_argument{"unknown revision: " + std::string{s}}; } +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; +} + static void from_json(const json::json& j, TestMultiTransaction& o) { if (j.contains("gasPrice")) @@ -255,37 +286,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(); From b4ab2f274d194f665b2042dcbbb79ea078cdafc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Wed, 8 Feb 2023 13:00:22 +0100 Subject: [PATCH 3/4] statetest: Reuse Transaction loading in TestMultiTransaction --- test/statetest/statetest_loader.cpp | 46 ++++++++++++----------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/test/statetest/statetest_loader.cpp b/test/statetest/statetest_loader.cpp index 1e1688b6f1..c0a7579eb3 100644 --- a/test/statetest/statetest_loader.cpp +++ b/test/statetest/statetest_loader.cpp @@ -182,22 +182,18 @@ evmc_revision to_rev(std::string_view s) throw std::invalid_argument{"unknown revision: " + std::string{s}}; } -template <> -state::Transaction from_json(const json::json& j) +/// Load common parts of Transaction or TestMultiTransaction. +static void from_json_tx_common(const json::json& j, state::Transaction& o) { - 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 (const auto& to = j.at("to"); !to.get().empty()) + o.to = from_json(to); - if (j.contains("gasPrice")) + 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; } else @@ -206,30 +202,26 @@ state::Transaction from_json(const json::json& j) o.max_gas_price = from_json(j.at("maxFeePerGas")); o.max_priority_gas_price = from_json(j.at("maxPriorityFeePerGas")); } +} + +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 (j.contains("accessList")) - o.access_list = from_json(j.at("accessList")); + 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) { - 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"]); + from_json_tx_common(j, o); for (const auto& j_data : j.at("data")) o.inputs.emplace_back(from_json(j_data)); From 8749c51a138a6a9c942e8b6f60e33a6cfc59e4e7 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 13 Feb 2023 21:49:01 +0100 Subject: [PATCH 4/4] statetest: Error on parsing Transaction with mixed legacy/1559 contents --- test/statetest/statetest_loader.cpp | 5 +++++ test/unittests/statetest_loader_tx_test.cpp | 12 ++---------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/test/statetest/statetest_loader.cpp b/test/statetest/statetest_loader.cpp index c0a7579eb3..4a9106014e 100644 --- a/test/statetest/statetest_loader.cpp +++ b/test/statetest/statetest_loader.cpp @@ -195,6 +195,11 @@ static void from_json_tx_common(const json::json& j, state::Transaction& o) o.kind = state::Transaction::Kind::legacy; 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 { diff --git a/test/unittests/statetest_loader_tx_test.cpp b/test/unittests/statetest_loader_tx_test.cpp index 5b011c3856..f94f939026 100644 --- a/test/unittests/statetest_loader_tx_test.cpp +++ b/test/unittests/statetest_loader_tx_test.cpp @@ -101,14 +101,6 @@ TEST(statetest_loader, tx_confusing) "accessList": [] })"; - 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_EQ(tx.to, 0xc0c1_address); - EXPECT_EQ(tx.max_gas_price, 0x8081); - EXPECT_EQ(tx.max_priority_gas_price, 0x8081); - EXPECT_TRUE(tx.access_list.empty()); + EXPECT_THROW( + test::from_json(json::json::parse(input)), std::invalid_argument); }