Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions test/integration/evmone-cli/t8n/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ add_test(NAME ${PREFIX}/clean_output_dirs COMMAND ${CMAKE_COMMAND} -E rm -rf
${CMAKE_CURRENT_BINARY_DIR}/berlin_base_fee
${CMAKE_CURRENT_BINARY_DIR}/cancun_create_tx
${CMAKE_CURRENT_BINARY_DIR}/cancun_create_tx_trace
${CMAKE_CURRENT_BINARY_DIR}/cancun_y_parity
${CMAKE_CURRENT_BINARY_DIR}/prague_empty_requests
${CMAKE_CURRENT_BINARY_DIR}/prague_pre_state_only
${CMAKE_CURRENT_BINARY_DIR}/osaka_blob_schedule
Expand Down Expand Up @@ -257,6 +258,39 @@ set_tests_properties(
)


set(TEST_CASE cancun_y_parity)

add_test(
NAME ${PREFIX}/${TEST_CASE}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${TEST_CASE}
COMMAND
evmone-cli t8n
--state.fork Cancun
--state.reward 0
--state.chainid 1
--input.alloc alloc.json
--input.txs txs.json
--input.env env.json
--output.basedir ${CMAKE_CURRENT_BINARY_DIR}/${TEST_CASE}
--output.result out.json
)
set_tests_properties(
${PREFIX}/${TEST_CASE} PROPERTIES FIXTURES_REQUIRED "${TEST_CASE};outputs")

add_test(
NAME ${PREFIX}/${TEST_CASE}/out.json
COMMAND ${CMAKE_COMMAND} -E cat ${CMAKE_CURRENT_BINARY_DIR}/${TEST_CASE}/out.json
)
set_tests_properties(
${PREFIX}/${TEST_CASE}/out.json PROPERTIES
FIXTURES_CLEANUP ${TEST_CASE}
# The EIP-1559 transaction names its signature's y-parity as `yParity` and carries no `v`, as
# go-ethereum's JSON does. Loading it is the run above; this is the transaction executing.
PASS_REGULAR_EXPRESSION [=["gasUsed": "0x5208"]=]
FAIL_REGULAR_EXPRESSION [=["error"]=]
)


add_test(NAME ${PREFIX}/bad_fork COMMAND evmone-cli t8n --state.fork NoSuchRev)
set_tests_properties(
${PREFIX}/bad_fork PROPERTIES PASS_REGULAR_EXPRESSION "unknown revision")
Expand Down
8 changes: 8 additions & 0 deletions test/integration/evmone-cli/t8n/cancun_y_parity/alloc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
"code": "0x",
"nonce": "0x0",
"balance": "0xde0b6b3a7640000",
"storage": {}
}
}
11 changes: 11 additions & 0 deletions test/integration/evmone-cli/t8n/cancun_y_parity/env.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"currentCoinbase": "0x8888f1f195afa192cfee860698584c030f4c9db1",
"currentNumber": "0x01",
"currentTimestamp": "0x54c99069",
"currentGasLimit": "0x2fefd8",
"currentRandom": "0x0000000000000000000000000000000000000000000000000000000000000001",
"parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"currentBaseFee": "0x1",
"currentExcessBlobGas": "0x0",
"withdrawals": []
}
19 changes: 19 additions & 0 deletions test/integration/evmone-cli/t8n/cancun_y_parity/txs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[
{
"accessList": [],
"chainId": "0x1",
"gas": "0x5208",
"hash": "0x3a01c573d8ea83d8872a79d7afe20bd009a61039da1347b64f0b3972ebc6a911",
"input": "0x",
"maxFeePerGas": "0x1",
"maxPriorityFeePerGas": "0x0",
"nonce": "0x0",
"r": "0x1043c2860f2cc884d4ce573c89fd36e7b68ff41480a17693f1e1349a5837e9fe",
"s": "0x78b469d7feac811d12dffc12bfefafc27e36e893042cb11c5c24a2e3e35b94a4",
"sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"to": "0x095E7BAea6a6c7c4c2DfeB977eFac326aF552d87",
"type": "0x2",
"value": "0x0",
"yParity": "0x1"
}
]
35 changes: 35 additions & 0 deletions test/unittests/statetest_loader_tx_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,41 @@ TEST(statetest_loader, tx_eip1559)
EXPECT_EQ(tx.v, 1);
}

TEST(statetest_loader, tx_y_parity)
{
// go-ethereum names a typed transaction's `v` as `yParity`; the two must agree when both are
// given. A legacy transaction's `v` also carries the chain id, so `yParity` cannot name it.
constexpr std::string_view typed = R"({
"input": "", "gas": "0x5208", "value": "0", "sender": "a0a1", "to": "c0c1",
"maxFeePerGas": "1", "maxPriorityFeePerGas": "0", "accessList": [], "nonce": "0",
"r": "0x1111111111111111111111111111111111111111111111111111111111111111",
"s": "0x2222222222222222222222222222222222222222222222222222222222222222",
"type": "2")";
constexpr std::string_view legacy = R"({
"input": "", "gas": "0x5208", "value": "0", "sender": "a0a1", "to": "c0c1",
"gasPrice": "1", "nonce": "0",
"r": "0x1111111111111111111111111111111111111111111111111111111111111111",
"s": "0x2222222222222222222222222222222222222222222222222222222222222222")";

const auto load = [](std::string_view head, std::string_view signature) {
return test::from_json<state::Transaction>(
json::json::parse(std::string{head} + ", " + std::string{signature} + "}"));
};

EXPECT_EQ(load(typed, R"("yParity": "0x1")").v, 1);
EXPECT_EQ(load(typed, R"("yParity": "0x0")").v, 0);
EXPECT_EQ(load(typed, R"("v": "0x1")").v, 1);
EXPECT_EQ(load(typed, R"("v": "0x1", "yParity": "0x1")").v, 1);

EXPECT_THAT([&] { load(typed, R"("v": "0x0", "yParity": "0x1")"); },
ThrowsMessage<std::invalid_argument>("invalid transaction: v and yParity do not match"));
EXPECT_THAT([&] { load(typed, R"("yParity": "0x2")"); },
ThrowsMessage<std::invalid_argument>("invalid transaction: yParity must be 0 or 1"));

EXPECT_EQ(load(legacy, R"("v": "0x1b", "yParity": "0x0")").v, 27); // yParity is ignored.
EXPECT_THROW(load(legacy, R"("yParity": "0x1")"), json::json::out_of_range);
}

TEST(statetest_loader, tx_access_list)
{
constexpr std::string_view input = R"({
Expand Down
27 changes: 25 additions & 2 deletions test/utils/statetest_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,23 @@ TestState from_json<TestState>(const json::json& j)
return o;
}

/// Loads the `yParity` of a typed transaction, the name go-ethereum's JSON gives its signature's
/// `v`. Returns nothing for a legacy transaction, whose `v` also carries the chain id, or when the
/// key is absent; a `v` given alongside must agree with it.
static std::optional<uint64_t> load_y_parity(const json::json& j, state::Transaction::Type type)
{
if (type == state::Transaction::Type::legacy)
return std::nullopt;
const auto y_parity = load_optional<uint64_t>(j, "yParity");
if (!y_parity.has_value())
return std::nullopt;
if (*y_parity > 1)
throw std::invalid_argument("invalid transaction: yParity must be 0 or 1");
if (const auto v = load_optional<uint64_t>(j, "v"); v.has_value() && *v != *y_parity)
throw std::invalid_argument("invalid transaction: v and yParity do not match");
return y_parity;
}

/// Load common parts of Transaction or TestMultiTransaction.
static void from_json_tx_common(const json::json& j, state::Transaction& o)
{
Expand Down Expand Up @@ -416,7 +433,10 @@ state::Transaction from_json<state::Transaction>(const json::json& j)

o.r = from_json<intx::uint256>(j.at("r"));
o.s = from_json<intx::uint256>(j.at("s"));
o.v = from_json<uint64_t>(j.at("v"));
if (const auto y_parity = load_y_parity(j, o.type))
o.v = *y_parity;
else
o.v = from_json<uint64_t>(j.at("v"));

return o;
}
Expand All @@ -442,7 +462,10 @@ static void from_json(const json::json& j, TestMultiTransaction& o)
for (const auto& j_value : j.at("value"))
o.values.emplace_back(from_json<intx::uint256>(j_value));

o.v = load_or<uint64_t>(j, "v", 0);
if (const auto y_parity = load_y_parity(j, o.type))
o.v = *y_parity;
else
o.v = load_or<uint64_t>(j, "v", 0);
}

static void from_json(const json::json& j, TestMultiTransaction::Indexes& o)
Expand Down