Skip to content
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

test: Add some mising State Test export features #807

Merged
merged 3 commits into from
Feb 28, 2024
Merged
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
2 changes: 1 addition & 1 deletion test/integration/statetest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ set_tests_properties(
add_test(
NAME ${PREFIX}/execute_exported_tests
# TODO: Broken exported tests are filtered out.
COMMAND evmone-statetest ${EXPORT_DIR}/state_transition --gtest_filter=-*block.*:*tx.tx_non_existing_sender
COMMAND evmone-statetest ${EXPORT_DIR}/state_transition --gtest_filter=-*block.*
)
set_tests_properties(
${PREFIX}/execute_exported_tests PROPERTIES
Expand Down
59 changes: 56 additions & 3 deletions test/unittests/state_transition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,22 @@ class TraceCapture

void state_transition::TearDown()
{
// Validation:

if (rev < EVMC_LONDON)
{
ASSERT_EQ(block.base_fee, 0);
ASSERT_EQ(tx.type, state::Transaction::Type::legacy);
}
if (tx.type == state::Transaction::Type::legacy)
{
ASSERT_EQ(tx.max_gas_price, tx.max_priority_gas_price);
}

validate_state(pre, rev);

// Execution:

auto state = pre;
const auto trace = !expect.trace.empty();
auto& selected_vm = trace ? tracing_vm : vm;
Expand Down Expand Up @@ -135,6 +149,23 @@ void state_transition::TearDown()
export_state_test(receipt, state);
}

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 TransactionReceipt& receipt, const State& post)
{
json::json j;
Expand All @@ -155,14 +186,36 @@ void state_transition::export_state_test(const TransactionReceipt& receipt, cons
jtx["sender"] = hex0x(tx.sender);
jtx["secretKey"] = hex0x(SenderSecretKey);
jtx["nonce"] = hex0x(tx.nonce);
jtx["maxFeePerGas"] = hex0x(tx.max_gas_price);
jtx["maxPriorityFeePerGas"] = hex0x(tx.max_priority_gas_price);
if (rev < EVMC_LONDON)
{
assert(tx.max_gas_price == tx.max_priority_gas_price);
jtx["gasPrice"] = hex0x(tx.max_gas_price);
}
else
{
jtx["maxFeePerGas"] = hex0x(tx.max_gas_price);
jtx["maxPriorityFeePerGas"] = hex0x(tx.max_priority_gas_price);
}

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

auto& jpost = jt["post"][evmc::to_string(rev)][0];
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));
}
}

auto& jpost = jt["post"][to_test_fork_name(rev)][0];
jpost["indexes"] = {{"data", 0}, {"gas", 0}, {"value", 0}};
jpost["hash"] = hex0x(mpt_hash(post.get_accounts()));
jpost["logs"] = hex0x(logs_hash(receipt.logs));
Expand Down
2 changes: 1 addition & 1 deletion test/unittests/state_transition.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class state_transition : public ExportableFixture
Transaction tx{
.gas_limit = block.gas_limit,
.max_gas_price = block.base_fee + 1,
.max_priority_gas_price = 1,
.max_priority_gas_price = block.base_fee + 1,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this change? This transaction field in the spec is called max_priority_fee_per_gas and is what is added on top of block base fee.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I guess because default is legacy transaction? It's confusing that Transaction has this field for legacy.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Legacy transaction requires these values to be the same. So I made the default agree as well.

Maybe this is not great for clarity of tests, but EIP-1559 calculations work for legacy transactions if these two fields have the same value of legacy gas price.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this is not great for clarity of tests, but EIP-1559 calculations work for legacy transactions if these two fields have the same value of legacy gas price.

Can't these calculations check transaction type and ignore this field (i.e. consider it equal max_gas_price) for legacy?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or another idea is that Transaction constructor for legacy should enforce this

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes they can, but there is no time to do this change now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then at least add

assert(tx.type != Legacy || tx.max_priority_gas_price == tx.max_gas_price);

to state transition.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it also can be just

if (tx.type == Legacy)
  tx.max_priority_gas_price = tx.max_gas_price;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I improved the asserts in TearDown() a bit.

It looks to me that we should have something like TestBlock with every field std::optional and set default values only after test definition. Otherwise, the test definitions must fight with defaults.

.sender = Sender,
.nonce = 1,
};
Expand Down
3 changes: 1 addition & 2 deletions test/unittests/state_transition_eof_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ TEST_F(state_transition, eof_invalid_initcode)

expect.post[tx.sender].nonce = pre.get(tx.sender).nonce + 1;
expect.post[tx.sender].balance =
pre.get(tx.sender).balance -
(block.base_fee + tx.max_priority_gas_price) * static_cast<uint64_t>(*expect.gas_used);
pre.get(tx.sender).balance - tx.max_gas_price * static_cast<uint64_t>(*expect.gas_used);
expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; // CREATE caller's nonce must be bumped
expect.post[*tx.to].storage[0x01_bytes32] = 0x00_bytes32; // CREATE must fail
expect.post[create_address].exists = false;
Expand Down
71 changes: 58 additions & 13 deletions test/unittests/state_transition_tx_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,57 +3,102 @@
// SPDX-License-Identifier: Apache-2.0

#include "state_transition.hpp"
#include <test/utils/bytecode.hpp>

using namespace evmc::literals;
using namespace evmone::test;

TEST_F(state_transition, tx_legacy)
{
rev = EVMC_ISTANBUL;
block.base_fee = 0; // should be 0 before London
tx.to = To;

expect.post.at(Sender).nonce = pre.get(Sender).nonce + 1;
}

TEST_F(state_transition, tx_non_existing_sender)
{
rev = EVMC_BERLIN;
block.base_fee = 0; // should be 0 before London
tx.to = To;
tx.max_gas_price = 0;
tx.max_priority_gas_price = 0;
tx.nonce = 0;
block.base_fee = 0;
pre.get_accounts().erase(Sender);

rev = EVMC_BERLIN;

expect.status = EVMC_SUCCESS;
expect.post.at(Sender).nonce = 1;
expect.post.at(Coinbase).exists = false;
}

TEST_F(state_transition, invalid_tx_non_existing_sender)
{
rev = EVMC_BERLIN;
block.base_fee = 0; // should be 0 before London
tx.to = To;
tx.max_gas_price = 1;
tx.max_priority_gas_price = 1;
tx.nonce = 0;
block.base_fee = 1;
pre.get_accounts().erase(Sender);

rev = EVMC_BERLIN;

expect.tx_error = INSUFFICIENT_FUNDS;
}

TEST_F(state_transition, blob_tx_insuficient_funds)
TEST_F(state_transition, tx_blob_gas_price)
{
rev = EVMC_CANCUN;
tx.to = To;
tx.gas_limit = 25000;
tx.max_gas_price = 1;
tx.max_gas_price = block.base_fee; // minimal gas price to make it
tx.max_priority_gas_price = 0;
tx.nonce = 1;
tx.type = Transaction::Type::blob;
tx.blob_hashes.emplace_back(
0x0100000000000000000000000000000000000000000000000000000000000000_bytes32);
tx.max_blob_gas_price = 1;
block.base_fee = 1;

pre.get_accounts()[tx.sender].balance = 0x20000 + 25000;
pre.get(tx.sender).balance = 0x20000 + tx.gas_limit * tx.max_gas_price;

rev = EVMC_CANCUN;

expect.post.at(Coinbase).exists = false;
expect.post.at(Coinbase).exists = false; // all gas is burned, Coinbase gets nothing
expect.status = EVMC_SUCCESS;
}

TEST_F(state_transition, empty_coinbase_fee_0_sd)
{
rev = EVMC_SPURIOUS_DRAGON;
block_reward = 0;
block.base_fee = 0; // should be 0 before London
tx.max_gas_price = 0;
tx.max_priority_gas_price = 0;
tx.to = To;
pre.insert(Coinbase, {});
expect.post[To].exists = false;
expect.post[Coinbase].exists = false;
}

TEST_F(state_transition, empty_coinbase_fee_0_tw)
{
rev = EVMC_TANGERINE_WHISTLE;
block_reward = 0;
block.base_fee = 0; // should be 0 before London
tx.max_gas_price = 0;
tx.max_priority_gas_price = 0;
tx.to = To;
pre.insert(Coinbase, {});
expect.post[To].exists = true;
expect.post[Coinbase].balance = 0;
}

TEST_F(state_transition, access_list_storage)
{
tx.to = To;
tx.access_list = {{To, {0x01_bytes32}}};

pre.insert(To,
{.storage = {{0x01_bytes32, {0x01_bytes32, 0x01_bytes32}}}, .code = sstore(2, sload(1))});

expect.post[To].storage[0x01_bytes32] = 0x01_bytes32;
expect.post[To].storage[0x02_bytes32] = 0x01_bytes32;
expect.gas_used = 47506; // Without access list: 45206
}
4 changes: 2 additions & 2 deletions test/utils/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ evmc_revision to_rev(std::string_view s)
return EVMC_FRONTIER;
if (s == "Homestead")
return EVMC_HOMESTEAD;
if (s == "EIP150")
if (s == "Tangerine Whistle" || s == "EIP150")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may not be the best idea because e.g. geth doesn't understand "Tangerine Whistle".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this to export as EIP150 and EIP158... We still accept full revision names.

return EVMC_TANGERINE_WHISTLE;
if (s == "EIP158")
if (s == "Spurious Dragon" || s == "EIP158")
return EVMC_SPURIOUS_DRAGON;
if (s == "Byzantium")
return EVMC_BYZANTIUM;
Expand Down