From 93305c72147cca8469306bb70b06d690b88e392b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Wed, 17 Jun 2026 14:38:29 +0200 Subject: [PATCH] test: Load transaction signature v as uint64 A legacy EIP-155 transaction encodes `v` as chainId*2 + 35 + parity, so `v` exceeds 0xff for any chainId >= 110 (e.g. chainId 300 gives v=0x27c). The state-test loader narrowed `v` to `uint8_t` and `Transaction::v` was `uint8_t`, so `from_json` threw `value > 0xFF` and `evmone t8n` rejected such transactions while geth `evm t8n` executes them. Widen `Transaction::v` and its loader to `uint64_t`, matching the `chainId` fix (#1570). `v` is only RLP-encoded for the transaction hash, so the wider type is encoded correctly with no other behavior change. Fixes #1487. --- test/state/transaction.hpp | 2 +- test/unittests/statetest_loader_tx_test.cpp | 26 ++++++++++++++ test/unittests/tooling_t8n_test.cpp | 39 +++++++++++++++++++++ test/utils/statetest_loader.cpp | 2 +- 4 files changed, 67 insertions(+), 2 deletions(-) diff --git a/test/state/transaction.hpp b/test/state/transaction.hpp index 701f66f112..27ead99759 100644 --- a/test/state/transaction.hpp +++ b/test/state/transaction.hpp @@ -78,7 +78,7 @@ struct Transaction uint64_t nonce = 0; intx::uint256 r; intx::uint256 s; - uint8_t v = 0; + uint64_t v = 0; AuthorizationList authorization_list; }; diff --git a/test/unittests/statetest_loader_tx_test.cpp b/test/unittests/statetest_loader_tx_test.cpp index 0dc739fdf8..3576f0ae3e 100644 --- a/test/unittests/statetest_loader_tx_test.cpp +++ b/test/unittests/statetest_loader_tx_test.cpp @@ -95,6 +95,32 @@ TEST(statetest_loader, tx_max_chain_id) EXPECT_EQ(tx.chain_id, std::numeric_limits::max()); } +TEST(statetest_loader, tx_max_legacy_chain_id) +{ + // A legacy EIP-155 transaction encodes the chain ID inside `v` as + // v = chain_id*2 + 35 + parity (parity in {0, 1}). Because `v` is loaded as uint64, the + // largest chain ID a legacy transaction can carry is the one whose `v` reaches uint64 max: + // chain_id = 0x7fffffffffffffee with parity 0 gives v = 0xffffffffffffffff. A larger chain ID + // would overflow `v`. + constexpr std::string_view input = R"({ + "input": "b0b1", + "gas": "0x9091", + "chainId": "0x7fffffffffffffee", + "value": "0xe0e1", + "sender": "a0a1", + "gasPrice": "0x7071", + "nonce": "0", + "r": "0x1111111111111111111111111111111111111111111111111111111111111111", + "s": "0x2222222222222222222222222222222222222222222222222222222222222222", + "v": "0xffffffffffffffff" + })"; + + const auto tx = test::from_json(json::json::parse(input)); + EXPECT_EQ(tx.chain_id, 0x7fffffffffffffee); + EXPECT_EQ(tx.v, std::numeric_limits::max()); + EXPECT_EQ(tx.v, tx.chain_id * 2 + 35); // EIP-155 with y-parity 0. +} + TEST(statetest_loader, tx_eip1559) { constexpr std::string_view input = R"({ diff --git a/test/unittests/tooling_t8n_test.cpp b/test/unittests/tooling_t8n_test.cpp index b45453716f..c43b151d7a 100644 --- a/test/unittests/tooling_t8n_test.cpp +++ b/test/unittests/tooling_t8n_test.cpp @@ -248,3 +248,42 @@ TEST(tooling_t8n, max_chain_id) EXPECT_NO_THROW(tooling::t8n(vm, args)); EXPECT_THAT(out_result.str(), HasSubstr("\"transactionHash\"")); } + +TEST(tooling_t8n, max_v) +{ + evmc::VM vm{evmc_create_evmone()}; + + // Legacy EIP-155 `v` is chainId*2 + 35 + parity, exceeding 0xff for chainId > 110. + // The maximum `v` (uint64 max = 0xffffffffffffffff) must be parsed and executed without + // overflow; regression test for `v` being loaded as `uint8_t`, which threw + // `from_json: value > 0xFF`. + static constexpr auto TX_MAX_V = R"([{ + "to": null, + "input": "0x60015ff3", + "gas": "0x186a0", + "nonce": "0x0", + "value": "0x0", + "gasPrice": "0x32", + "chainId": "0x1", + "sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "v": "0xffffffffffffffff", + "r": "0x468a915f087692bb9be503831a3dfef2cf9c8dee26deb40ff2ec99e8d22665ae", + "s": "0x5cedae0810c3851ecd1004bfdbfe6ddc7753c2d665993bb01ce75af7857b13dc" + }])"; + + std::istringstream env{ENV_JSON}; + std::istringstream alloc{ALLOC_JSON}; + std::istringstream txs{TX_MAX_V}; + std::ostringstream out_result; + + tooling::T8NArgs args; + args.rev = EVMC_SHANGHAI; + args.chain_id = 1; + args.alloc = &alloc; + args.env = &env; + args.txs = &txs; + args.out_result = &out_result; + + EXPECT_NO_THROW(tooling::t8n(vm, args)); + EXPECT_THAT(out_result.str(), HasSubstr("\"transactionHash\"")); +} diff --git a/test/utils/statetest_loader.cpp b/test/utils/statetest_loader.cpp index 6ed2302696..d6c8c7f275 100644 --- a/test/utils/statetest_loader.cpp +++ b/test/utils/statetest_loader.cpp @@ -456,7 +456,7 @@ state::Transaction from_json(const json::json& j) o.nonce = from_json(j.at("nonce")); o.r = from_json(j.at("r")); o.s = from_json(j.at("s")); - o.v = from_json(j.at("v")); + o.v = from_json(j.at("v")); return o; }