Skip to content

Commit 93305c7

Browse files
committed
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<uint8_t>` 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.
1 parent f200c0d commit 93305c7

4 files changed

Lines changed: 67 additions & 2 deletions

File tree

test/state/transaction.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ struct Transaction
7878
uint64_t nonce = 0;
7979
intx::uint256 r;
8080
intx::uint256 s;
81-
uint8_t v = 0;
81+
uint64_t v = 0;
8282
AuthorizationList authorization_list;
8383
};
8484

test/unittests/statetest_loader_tx_test.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,32 @@ TEST(statetest_loader, tx_max_chain_id)
9595
EXPECT_EQ(tx.chain_id, std::numeric_limits<uint64_t>::max());
9696
}
9797

98+
TEST(statetest_loader, tx_max_legacy_chain_id)
99+
{
100+
// A legacy EIP-155 transaction encodes the chain ID inside `v` as
101+
// v = chain_id*2 + 35 + parity (parity in {0, 1}). Because `v` is loaded as uint64, the
102+
// largest chain ID a legacy transaction can carry is the one whose `v` reaches uint64 max:
103+
// chain_id = 0x7fffffffffffffee with parity 0 gives v = 0xffffffffffffffff. A larger chain ID
104+
// would overflow `v`.
105+
constexpr std::string_view input = R"({
106+
"input": "b0b1",
107+
"gas": "0x9091",
108+
"chainId": "0x7fffffffffffffee",
109+
"value": "0xe0e1",
110+
"sender": "a0a1",
111+
"gasPrice": "0x7071",
112+
"nonce": "0",
113+
"r": "0x1111111111111111111111111111111111111111111111111111111111111111",
114+
"s": "0x2222222222222222222222222222222222222222222222222222222222222222",
115+
"v": "0xffffffffffffffff"
116+
})";
117+
118+
const auto tx = test::from_json<state::Transaction>(json::json::parse(input));
119+
EXPECT_EQ(tx.chain_id, 0x7fffffffffffffee);
120+
EXPECT_EQ(tx.v, std::numeric_limits<uint64_t>::max());
121+
EXPECT_EQ(tx.v, tx.chain_id * 2 + 35); // EIP-155 with y-parity 0.
122+
}
123+
98124
TEST(statetest_loader, tx_eip1559)
99125
{
100126
constexpr std::string_view input = R"({

test/unittests/tooling_t8n_test.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,3 +248,42 @@ TEST(tooling_t8n, max_chain_id)
248248
EXPECT_NO_THROW(tooling::t8n(vm, args));
249249
EXPECT_THAT(out_result.str(), HasSubstr("\"transactionHash\""));
250250
}
251+
252+
TEST(tooling_t8n, max_v)
253+
{
254+
evmc::VM vm{evmc_create_evmone()};
255+
256+
// Legacy EIP-155 `v` is chainId*2 + 35 + parity, exceeding 0xff for chainId > 110.
257+
// The maximum `v` (uint64 max = 0xffffffffffffffff) must be parsed and executed without
258+
// overflow; regression test for `v` being loaded as `uint8_t`, which threw
259+
// `from_json<uint8_t>: value > 0xFF`.
260+
static constexpr auto TX_MAX_V = R"([{
261+
"to": null,
262+
"input": "0x60015ff3",
263+
"gas": "0x186a0",
264+
"nonce": "0x0",
265+
"value": "0x0",
266+
"gasPrice": "0x32",
267+
"chainId": "0x1",
268+
"sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
269+
"v": "0xffffffffffffffff",
270+
"r": "0x468a915f087692bb9be503831a3dfef2cf9c8dee26deb40ff2ec99e8d22665ae",
271+
"s": "0x5cedae0810c3851ecd1004bfdbfe6ddc7753c2d665993bb01ce75af7857b13dc"
272+
}])";
273+
274+
std::istringstream env{ENV_JSON};
275+
std::istringstream alloc{ALLOC_JSON};
276+
std::istringstream txs{TX_MAX_V};
277+
std::ostringstream out_result;
278+
279+
tooling::T8NArgs args;
280+
args.rev = EVMC_SHANGHAI;
281+
args.chain_id = 1;
282+
args.alloc = &alloc;
283+
args.env = &env;
284+
args.txs = &txs;
285+
args.out_result = &out_result;
286+
287+
EXPECT_NO_THROW(tooling::t8n(vm, args));
288+
EXPECT_THAT(out_result.str(), HasSubstr("\"transactionHash\""));
289+
}

test/utils/statetest_loader.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ state::Transaction from_json<state::Transaction>(const json::json& j)
456456
o.nonce = from_json<uint64_t>(j.at("nonce"));
457457
o.r = from_json<intx::uint256>(j.at("r"));
458458
o.s = from_json<intx::uint256>(j.at("s"));
459-
o.v = from_json<uint8_t>(j.at("v"));
459+
o.v = from_json<uint64_t>(j.at("v"));
460460

461461
return o;
462462
}

0 commit comments

Comments
 (0)