diff --git a/test/state/rlp.hpp b/test/state/rlp.hpp index 574ae73ce9..c288c992eb 100644 --- a/test/state/rlp.hpp +++ b/test/state/rlp.hpp @@ -98,6 +98,13 @@ inline bytes encode_tuple(const Types&... elements) return internal::wrap_list((encode(elements) + ...)); } +/// Encodes a pair of values as RPL list. +template +inline bytes encode(const std::pair& p) +{ + return encode_tuple(p.first, p.second); +} + /// Encodes the container as RLP list. /// /// @tparam InputIterator Type of the input iterator. diff --git a/test/state/state.cpp b/test/state/state.cpp index f838afec98..ebcaf022d1 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -203,11 +203,22 @@ std::variant transition( return rlp::encode_tuple(tx.nonce, tx.max_gas_price, static_cast(tx.gas_limit), tx.to.has_value() ? tx.to.value() : bytes_view(), tx.value, tx.data, tx.v, tx.r, tx.s); } + else if (tx.kind == Transaction::Kind::eip2930) + { + if (tx.v > 1) + throw std::invalid_argument("`v` value for eip2930 transaction must be 0 or 1"); + // tx_type + + // rlp [nonce, gas_price, gas_limit, to, value, data, access_list, v, r, s]; + return bytes{0x01} + // Transaction type (eip2930 type == 1) + rlp::encode_tuple(tx.chain_id, tx.nonce, tx.max_gas_price, + static_cast(tx.gas_limit), + tx.to.has_value() ? tx.to.value() : bytes_view(), tx.value, tx.data, + tx.access_list, static_cast(tx.v), tx.r, tx.s); + } else { if (tx.v > 1) throw std::invalid_argument("`v` value for eip1559 transaction must be 0 or 1"); - // TODO: Implement AccessList encoding // tx_type + // rlp [chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, // data, access_list, sig_parity, r, s]; @@ -215,7 +226,7 @@ std::variant transition( rlp::encode_tuple(tx.chain_id, tx.nonce, tx.max_priority_gas_price, tx.max_gas_price, static_cast(tx.gas_limit), tx.to.has_value() ? tx.to.value() : bytes_view(), tx.value, tx.data, - std::vector(), static_cast(tx.v), tx.r, tx.s); + tx.access_list, static_cast(tx.v), tx.r, tx.s); } } diff --git a/test/state/state.hpp b/test/state/state.hpp index 82bbf8a5b6..192ce83ac6 100644 --- a/test/state/state.hpp +++ b/test/state/state.hpp @@ -80,10 +80,11 @@ using AccessList = std::vector>>; struct Transaction { - enum class Kind + enum class Kind : uint8_t { - legacy, - eip1559 + legacy = 0, + eip2930 = 1, ///< Transaction with access list https://eips.ethereum.org/EIPS/eip-2930 + eip1559 = 2 ///< EIP1559 transaction https://eips.ethereum.org/EIPS/eip-1559 }; Kind kind = Kind::legacy; diff --git a/test/statetest/statetest_loader.cpp b/test/statetest/statetest_loader.cpp index 9489d19012..7ca4321c14 100644 --- a/test/statetest/statetest_loader.cpp +++ b/test/statetest/statetest_loader.cpp @@ -2,6 +2,7 @@ // Copyright 2022 The evmone Authors. // SPDX-License-Identifier: Apache-2.0 +#include "../utils/stdx/utility.hpp" #include "statetest.hpp" #include #include @@ -249,7 +250,7 @@ static void from_json_tx_common(const json::json& j, state::Transaction& o) if (j.contains("maxFeePerGas") || j.contains("maxPriorityFeePerGas")) { throw std::invalid_argument( - "Misformatted transaction -- contains both legacy and 1559 fees"); + "invalid transaction: contains both legacy and EIP-1559 fees"); } } else @@ -265,12 +266,24 @@ state::Transaction from_json(const json::json& j) { state::Transaction o; from_json_tx_common(j, o); + if (const auto chain_id_it = j.find("chainId"); chain_id_it != j.end()) + o.chain_id = from_json(*chain_id_it); 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); + if (o.kind == state::Transaction::Kind::legacy) // Upgrade tx type if tx has "accessList" + o.kind = state::Transaction::Kind::eip2930; + } + + if (const auto type_it = j.find("type"); type_it != j.end()) + { + if (stdx::to_underlying(o.kind) != from_json(*type_it)) + throw std::invalid_argument("wrong transaction type"); + } o.nonce = from_json(j.at("nonce")); o.r = from_json(j.at("r")); diff --git a/test/unittests/state_rlp_test.cpp b/test/unittests/state_rlp_test.cpp index ac2c84c6ff..781665fa7a 100644 --- a/test/unittests/state_rlp_test.cpp +++ b/test/unittests/state_rlp_test.cpp @@ -2,7 +2,7 @@ // Copyright 2022 The evmone Authors. // SPDX-License-Identifier: Apache-2.0 -#include +#include #include #include #include @@ -12,6 +12,7 @@ using namespace evmone; using namespace evmc::literals; using namespace intx; +using namespace testing; static constexpr auto emptyBytesHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470_bytes32; @@ -375,5 +376,78 @@ TEST(state_rlp, tx_to_rlp_eip1559_invalid_v_value) tx.v = 2; tx.chain_id = 1; - EXPECT_THROW(rlp::encode(tx), std::invalid_argument); + EXPECT_THAT([tx]() { rlp::encode(tx); }, + ThrowsMessage("`v` value for eip1559 transaction must be 0 or 1")); +} + +TEST(state_rlp, tx_to_rlp_eip2930_invalid_v_value) +{ + state::Transaction tx{}; + tx.kind = evmone::state::Transaction::Kind::eip2930; + tx.data = ""_hex; + tx.gas_limit = 1; + tx.max_gas_price = 1; + tx.max_priority_gas_price = 1; + tx.sender = 0x0000000000000000000000000000000000000000_address; + tx.to = 0x0000000000000000000000000000000000000000_address; + tx.value = 0; + tx.access_list = {}; + tx.nonce = 47; + tx.r = 0x0000000000000000000000000000000000000000000000000000000000000000_u256; + tx.s = 0x0000000000000000000000000000000000000000000000000000000000000000_u256; + tx.v = 2; + tx.chain_id = 1; + + EXPECT_THAT([tx]() { rlp::encode(tx); }, + ThrowsMessage("`v` value for eip2930 transaction must be 0 or 1")); +} + +TEST(state_rlp, tx_to_rlp_eip1559_with_non_empty_access_list) +{ + state::Transaction tx{}; + tx.kind = evmone::state::Transaction::Kind::eip1559; + tx.data = "00"_hex; + tx.gas_limit = 0x3d0900; + tx.max_gas_price = 0x7d0; + tx.max_priority_gas_price = 0xa; + tx.sender = 0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b_address; + tx.to = 0xcccccccccccccccccccccccccccccccccccccccc_address; + tx.value = 0; + tx.access_list = {{0xcccccccccccccccccccccccccccccccccccccccc_address, + {0x0000000000000000000000000000000000000000000000000000000000000000_bytes32, + 0x0000000000000000000000000000000000000000000000000000000000000001_bytes32}}}; + tx.nonce = 1; + tx.r = 0xd671815898b8dd34321adbba4cb6a57baa7017323c26946f3719b00e70c755c2_u256; + tx.s = 0x3528b9efe3be57ea65a933d1e6bbf3b7d0c78830138883c1201e0c641fee6464_u256; + tx.v = 0; + tx.chain_id = 1; + + EXPECT_EQ(keccak256(rlp::encode(tx)), + 0xfb18421827800adcf465688e303cc9863045fdb96971473a114677916a3a08a4_bytes32); +} + +TEST(state_rlp, tx_to_rlp_eip2930_with_non_empty_access_list) +{ + // https://etherscan.io/tx/0xf076e75aa935552e20e5d9fd4d1dda4ff33399ff3d6ac22843ae646f82c385d4 + + state::Transaction tx{}; + tx.kind = evmone::state::Transaction::Kind::eip2930; + tx.data = + "0x095ea7b3000000000000000000000000f17d23136b4fead139f54fb766c8795faae09660ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"_hex; + tx.gas_limit = 51253; + tx.max_gas_price = 15650965396; + tx.max_priority_gas_price = 15650965396; + tx.sender = 0xcb0b99284784d9e400b1020b01fc40ff193d3540_address; + tx.to = 0x9232a548dd9e81bac65500b5e0d918f8ba93675c_address; + tx.value = 0; + tx.access_list = {{0x9232a548dd9e81bac65500b5e0d918f8ba93675c_address, + {0x8e947fe742892ee6fffe7cfc013acac35d33a3892c58597344bed88b21eb1d2f_bytes32}}}; + tx.nonce = 62; + tx.r = 0x2cfaa5ffa42172bfa9f83207a257c53ba3a106844ee58e9131466f655ecc11e9_u256; + tx.s = 0x419366dadd905a16cd433f2953f9ed976560822bb2611ac192b939f7b9c2a98c_u256; + tx.v = 1; + tx.chain_id = 1; + + EXPECT_EQ(keccak256(rlp::encode(tx)), + 0xf076e75aa935552e20e5d9fd4d1dda4ff33399ff3d6ac22843ae646f82c385d4_bytes32); } diff --git a/test/unittests/statetest_loader_tx_test.cpp b/test/unittests/statetest_loader_tx_test.cpp index 07a1c50526..fddec80c90 100644 --- a/test/unittests/statetest_loader_tx_test.cpp +++ b/test/unittests/statetest_loader_tx_test.cpp @@ -2,12 +2,13 @@ // Copyright 2023 The evmone Authors. // SPDX-License-Identifier: Apache-2.0 -#include +#include #include #include using namespace evmone; using namespace intx; +using namespace testing; // TODO: Add specific test of loading nonce, chainId, r, s, v @@ -16,6 +17,7 @@ TEST(statetest_loader, tx_create_legacy) constexpr std::string_view input = R"({ "input": "b0b1", "gas": "0x9091", + "chainId": "0x5", "value": "0xe0e1", "sender": "a0a1", "to": "", @@ -30,6 +32,7 @@ TEST(statetest_loader, tx_create_legacy) EXPECT_EQ(tx.kind, state::Transaction::Kind::legacy); EXPECT_EQ(tx.data, (bytes{0xb0, 0xb1})); EXPECT_EQ(tx.gas_limit, 0x9091); + EXPECT_EQ(tx.chain_id, 5); EXPECT_EQ(tx.value, 0xe0e1); EXPECT_EQ(tx.sender, 0xa0a1_address); EXPECT_FALSE(tx.to.has_value()); @@ -63,6 +66,7 @@ TEST(statetest_loader, tx_eip1559) EXPECT_EQ(tx.kind, state::Transaction::Kind::eip1559); EXPECT_EQ(tx.data, (bytes{0xb0, 0xb1})); EXPECT_EQ(tx.gas_limit, 0x9091); + EXPECT_EQ(tx.chain_id, 0); EXPECT_EQ(tx.value, 0xe0e1); EXPECT_EQ(tx.sender, 0xa0a1_address); EXPECT_EQ(tx.to, 0xc0c1_address); @@ -133,8 +137,117 @@ TEST(statetest_loader, tx_confusing) "v": "1" })"; - EXPECT_THROW( - test::from_json(json::json::parse(input)), std::invalid_argument); + EXPECT_THAT([&] { test::from_json(json::json::parse(input)); }, + ThrowsMessage( + "invalid transaction: contains both legacy and EIP-1559 fees")); +} + +TEST(statetest_loader, tx_type_1) +{ + constexpr std::string_view input = R"({ + "input": "", + "gas": "0", + "type": "1", + "value": "0", + "sender": "", + "to": "", + "gasPrice": "0", + "accessList": [ + {"address": "ac01", "storageKeys": []}, + {"address": "ac02", "storageKeys": ["fe", "00"]} + ], + "nonce": "0", + "r": "0x1111111111111111111111111111111111111111111111111111111111111111", + "s": "0x2222222222222222222222222222222222222222222222222222222222222222", + "v": "1" + })"; + + const auto tx = test::from_json(json::json::parse(input)); + EXPECT_EQ(tx.kind, state::Transaction::Kind::eip2930); + EXPECT_TRUE(tx.data.empty()); + EXPECT_EQ(tx.gas_limit, 0); + EXPECT_EQ(tx.value, 0); + EXPECT_EQ(tx.sender, 0x00_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})); + EXPECT_EQ(tx.nonce, 0); + EXPECT_EQ(tx.r, 0x1111111111111111111111111111111111111111111111111111111111111111_u256); + EXPECT_EQ(tx.s, 0x2222222222222222222222222222222222222222222222222222222222222222_u256); + EXPECT_EQ(tx.v, 1); +} + +TEST(statetest_loader, invalid_tx_type) +{ + { + constexpr std::string_view input = R"({ + "input": "", + "gas": "0", + "type": "2", + "value": "0", + "sender": "", + "to": "", + "gasPrice": "0", + "accessList": [ + {"address": "ac01", "storageKeys": []}, + {"address": "ac02", "storageKeys": ["fe", "00"]} + ], + "nonce": "0", + "r": "0x1111111111111111111111111111111111111111111111111111111111111111", + "s": "0x2222222222222222222222222222222222222222222222222222222222222222", + "v": "1" + })"; + + EXPECT_THAT([&] { test::from_json(json::json::parse(input)); }, + ThrowsMessage("wrong transaction type")); + } + { + constexpr std::string_view input = R"({ + "input": "", + "gas": "0", + "type": "1", + "value": "0", + "sender": "", + "to": "", + "gasPrice": "0", + "nonce": "0", + "r": "0x1111111111111111111111111111111111111111111111111111111111111111", + "s": "0x2222222222222222222222222222222222222222222222222222222222222222", + "v": "1" + })"; + + EXPECT_THAT([&] { test::from_json(json::json::parse(input)); }, + ThrowsMessage("wrong transaction type")); + } + + { + constexpr std::string_view input = R"({ + "input": "", + "gas": "0", + "type": "1", + "value": "0", + "sender": "", + "to": "", + "maxFeePerGas": "0", + "maxPriorityFeePerGas": "0", + "accessList": [ + {"address": "ac01", "storageKeys": []}, + {"address": "ac02", "storageKeys": ["fe", "00"]} + ], + "nonce": "0", + "r": "0x1111111111111111111111111111111111111111111111111111111111111111", + "s": "0x2222222222222222222222222222222222222222222222222222222222222222", + "v": "1" + })"; + + EXPECT_THAT([&] { test::from_json(json::json::parse(input)); }, + ThrowsMessage("wrong transaction type")); + } } namespace evmone::test