Skip to content

test: Move state test export procedure to testutils #1169

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
5 changes: 5 additions & 0 deletions test/statetest/statetest.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ state::Transaction from_json<state::Transaction>(const json::json& j);
/// Exports the State (accounts) to JSON format (aka pre/post/alloc state).
json::json to_json(const TestState& state);

/// Export the state test to JSON format.
json::json to_state_test(std::string_view test_name, const state::BlockInfo& block,
state::Transaction& tx, const TestState& pre, evmc_revision rev,
const std::variant<state::TransactionReceipt, std::error_code>& res, const TestState& post);

/// Returns the standardized error message for the transaction validation error.
[[nodiscard]] std::string get_invalid_tx_message(state::ErrorCode errc) noexcept;

Expand Down
127 changes: 127 additions & 0 deletions test/statetest/statetest_export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,27 @@
// SPDX-License-Identifier: Apache-2.0

#include "statetest.hpp"
#include <test/state/mpt_hash.hpp>

namespace evmone::test
{
namespace
{
/// Converts EVM revision to the fork name commonly used in tests.
std::string_view to_test_fork_name(evmc_revision rev) noexcept
{
switch (rev)
{
case EVMC_TANGERINE_WHISTLE:
return "EIP150";
case EVMC_SPURIOUS_DRAGON:
return "EIP158";
default:
return evmc::to_string(rev);
}
}
} // namespace

[[nodiscard]] std::string get_invalid_tx_message(state::ErrorCode errc) noexcept
{
using namespace state;
Expand Down Expand Up @@ -71,4 +89,113 @@ json::json to_json(const TestState& state)
}
return j;
}

json::json to_state_test(std::string_view test_name, const state::BlockInfo& block,
state::Transaction& tx, const TestState& pre, evmc_revision rev,
const std::variant<state::TransactionReceipt, std::error_code>& res, const TestState& post)
{
using state::Transaction;

// FIXME: Move to common place.
static constexpr auto SenderSecretKey =
0x00000000000000000000000000000000000000000000000000000002b1263d2b_bytes32;

json::json j;
auto& jt = j[test_name];

auto& jenv = jt["env"];
jenv["currentNumber"] = hex0x(block.number);
jenv["currentTimestamp"] = hex0x(block.timestamp);
jenv["currentGasLimit"] = hex0x(block.gas_limit);
jenv["currentCoinbase"] = hex0x(block.coinbase);
jenv["currentBaseFee"] = hex0x(block.base_fee);
jenv["currentRandom"] = hex0x(block.prev_randao);

jt["pre"] = to_json(pre);

auto& jtx = jt["transaction"];
if (tx.to.has_value())
jtx["to"] = hex0x(*tx.to);
jtx["sender"] = hex0x(tx.sender);
jtx["secretKey"] = hex0x(SenderSecretKey);
jtx["nonce"] = hex0x(tx.nonce);
if (tx.type >= Transaction::Type::eip1559)
{
jtx["maxFeePerGas"] = hex0x(tx.max_gas_price);
jtx["maxPriorityFeePerGas"] = hex0x(tx.max_priority_gas_price);
}
else
{
assert(tx.max_gas_price == tx.max_priority_gas_price);
jtx["gasPrice"] = hex0x(tx.max_gas_price);
}

jtx["data"][0] = hex0x(tx.data);
jtx["gasLimit"][0] = hex0x(tx.gas_limit);
jtx["value"][0] = hex0x(tx.value);

// Force `accessLists` output even if empty.
if (tx.type >= Transaction::Type::access_list)
jtx["accessLists"][0] = json::json::array();

if (!tx.access_list.empty())
{
auto& ja = jtx["accessLists"][0];
for (const auto& [addr, storage_keys] : tx.access_list)
{
json::json je;
je["address"] = hex0x(addr);
auto& jstorage_keys = je["storageKeys"] = json::json::array();
for (const auto& k : storage_keys)
jstorage_keys.emplace_back(hex0x(k));
ja.emplace_back(std::move(je));
}
}

if (tx.type == Transaction::Type::blob)
{
jtx["maxFeePerBlobGas"] = hex0x(tx.max_blob_gas_price);
jtx["blobVersionedHashes"] = json::json::array();
for (const auto& blob_hash : tx.blob_hashes)
{
jtx["blobVersionedHashes"].emplace_back(hex0x(blob_hash));
}
}

if (!tx.authorization_list.empty())
{
auto& ja = jtx["authorizationList"];
for (const auto& [chain_id, addr, nonce, signer, r, s, y_parity] : tx.authorization_list)
{
json::json je;
je["chainId"] = hex0x(chain_id);
je["address"] = hex0x(addr);
je["nonce"] = hex0x(nonce);
je["v"] = hex0x(y_parity);
je["r"] = hex0x(r);
je["s"] = hex0x(s);
if (signer.has_value())
je["signer"] = hex0x(*signer);
ja.emplace_back(std::move(je));
}
}


auto& jpost = jt["post"][to_test_fork_name(rev)][0];
jpost["indexes"] = {{"data", 0}, {"gas", 0}, {"value", 0}};
jpost["hash"] = hex0x(mpt_hash(post));

if (holds_alternative<std::error_code>(res))
{
jpost["expectException"] = get_invalid_tx_message(
static_cast<state::ErrorCode>(std::get<std::error_code>(res).value()));
jpost["logs"] = hex0x(logs_hash(std::vector<state::Log>()));
}
else
{
jpost["logs"] = hex0x(logs_hash(std::get<state::TransactionReceipt>(res).logs));
}

return j;
}
} // namespace evmone::test
115 changes: 2 additions & 113 deletions test/unittests/state_transition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,122 +150,11 @@ void state_transition::TearDown()
export_state_test(res, post);
}

namespace
{
/// Converts EVM revision to the fork name commonly used in tests.
std::string_view to_test_fork_name(evmc_revision rev) noexcept
{
switch (rev)
{
case EVMC_TANGERINE_WHISTLE:
return "EIP150";
case EVMC_SPURIOUS_DRAGON:
return "EIP158";
default:
return evmc::to_string(rev);
}
}
} // namespace

void state_transition::export_state_test(
const std::variant<TransactionReceipt, std::error_code>& res, const TestState& post)
{
json::json j;
auto& jt = j[export_test_name];

auto& jenv = jt["env"];
jenv["currentNumber"] = hex0x(block.number);
jenv["currentTimestamp"] = hex0x(block.timestamp);
jenv["currentGasLimit"] = hex0x(block.gas_limit);
jenv["currentCoinbase"] = hex0x(block.coinbase);
jenv["currentBaseFee"] = hex0x(block.base_fee);
jenv["currentRandom"] = hex0x(block.prev_randao);

jt["pre"] = to_json(pre);

auto& jtx = jt["transaction"];
if (tx.to.has_value())
jtx["to"] = hex0x(*tx.to);
jtx["sender"] = hex0x(tx.sender);
jtx["secretKey"] = hex0x(SenderSecretKey);
jtx["nonce"] = hex0x(tx.nonce);
if (tx.type >= Transaction::Type::eip1559)
{
jtx["maxFeePerGas"] = hex0x(tx.max_gas_price);
jtx["maxPriorityFeePerGas"] = hex0x(tx.max_priority_gas_price);
}
else
{
assert(tx.max_gas_price == tx.max_priority_gas_price);
jtx["gasPrice"] = hex0x(tx.max_gas_price);
}

jtx["data"][0] = hex0x(tx.data);
jtx["gasLimit"][0] = hex0x(tx.gas_limit);
jtx["value"][0] = hex0x(tx.value);

// Force `accessLists` output even if empty.
if (tx.type >= Transaction::Type::access_list)
jtx["accessLists"][0] = json::json::array();

if (!tx.access_list.empty())
{
auto& ja = jtx["accessLists"][0];
for (const auto& [addr, storage_keys] : tx.access_list)
{
json::json je;
je["address"] = hex0x(addr);
auto& jstorage_keys = je["storageKeys"] = json::json::array();
for (const auto& k : storage_keys)
jstorage_keys.emplace_back(hex0x(k));
ja.emplace_back(std::move(je));
}
}

if (tx.type == Transaction::Type::blob)
{
jtx["maxFeePerBlobGas"] = hex0x(tx.max_blob_gas_price);
jtx["blobVersionedHashes"] = json::json::array();
for (const auto& blob_hash : tx.blob_hashes)
{
jtx["blobVersionedHashes"].emplace_back(hex0x(blob_hash));
}
}

if (!tx.authorization_list.empty())
{
auto& ja = jtx["authorizationList"];
for (const auto& [chain_id, addr, nonce, signer, r, s, y_parity] : tx.authorization_list)
{
json::json je;
je["chainId"] = hex0x(chain_id);
je["address"] = hex0x(addr);
je["nonce"] = hex0x(nonce);
je["v"] = hex0x(y_parity);
je["r"] = hex0x(r);
je["s"] = hex0x(s);
if (signer.has_value())
je["signer"] = hex0x(*signer);
ja.emplace_back(std::move(je));
}
}


auto& jpost = jt["post"][to_test_fork_name(rev)][0];
jpost["indexes"] = {{"data", 0}, {"gas", 0}, {"value", 0}};
jpost["hash"] = hex0x(mpt_hash(post));

if (holds_alternative<std::error_code>(res))
{
jpost["expectException"] =
get_invalid_tx_message(static_cast<ErrorCode>(std::get<std::error_code>(res).value()));
jpost["logs"] = hex0x(logs_hash(std::vector<Log>()));
}
else
{
jpost["logs"] = hex0x(logs_hash(std::get<TransactionReceipt>(res).logs));
}

const auto j = to_state_test(
export_test_name, block, tx, pre, rev, res, post);
std::ofstream{export_file_path} << std::setw(2) << j;
}
} // namespace evmone::test