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: Introduce TestState and TestAccount #811

Merged
merged 1 commit into from
Jun 6, 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
6 changes: 3 additions & 3 deletions test/blockchaintest/blockchaintest.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "../state/bloom_filter.hpp"
#include "../state/state.hpp"
#include "../state/test_state.hpp"
#include "../utils/utils.hpp"
#include <evmc/evmc.hpp>
#include <span>
Expand Down Expand Up @@ -43,7 +44,6 @@ struct BlockHeader
struct TestBlock
{
state::BlockInfo block_info;
state::State pre_state;
std::vector<state::Transaction> transactions;

BlockHeader expected_block_header;
Expand All @@ -54,14 +54,14 @@ struct BlockchainTest
struct Expectation
{
hash256 last_block_hash;
std::variant<state::State, hash256> post_state;
std::variant<TestState, hash256> post_state;
};

std::string name;

std::vector<TestBlock> test_blocks;
BlockHeader genesis_block_header;
state::State pre_state;
TestState pre_state;
RevisionSchedule rev;

Expectation expectation;
Expand Down
4 changes: 2 additions & 2 deletions test/blockchaintest/blockchaintest_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ BlockchainTest load_blockchain_test_case(const std::string& name, const json::js
BlockchainTest bt;
bt.name = name;
bt.genesis_block_header = from_json<BlockHeader>(j.at("genesisBlockHeader"));
bt.pre_state = from_json<State>(j.at("pre"));
bt.pre_state = from_json<TestState>(j.at("pre"));
bt.rev = to_rev_schedule(j.at("network").get<std::string>());

for (const auto& el : j.at("blocks"))
Expand All @@ -128,7 +128,7 @@ BlockchainTest load_blockchain_test_case(const std::string& name, const json::js
bt.expectation.last_block_hash = from_json<hash256>(j.at("lastblockhash"));

if (const auto it = j.find("postState"); it != j.end())
bt.expectation.post_state = from_json<State>(*it);
bt.expectation.post_state = from_json<TestState>(*it);
else if (const auto it_hash = j.find("postStateHash"); it_hash != j.end())
bt.expectation.post_state = from_json<hash256>(*it_hash);

Expand Down
42 changes: 18 additions & 24 deletions test/blockchaintest/blockchaintest_runner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
cumulative_gas_used += receipt.gas_used;
receipt.cumulative_gas_used = cumulative_gas_used;
if (rev < EVMC_BYZANTIUM)
receipt.post_state = state::mpt_hash(state.get_accounts());
receipt.post_state = state::mpt_hash(TestState{state});

block_gas_left -= receipt.gas_used;
blob_gas_left -= tx.blob_gas_used();
Expand All @@ -91,13 +91,11 @@
return std::nullopt;
}

std::string print_state(const state::State& s)
std::string print_state(const TestState& s)

Check warning on line 94 in test/blockchaintest/blockchaintest_runner.cpp

View check run for this annotation

Codecov / codecov/patch

test/blockchaintest/blockchaintest_runner.cpp#L94

Added line #L94 was not covered by tests
{
std::stringstream out;
const std::map<address, state::Account> ordered(
s.get_accounts().begin(), s.get_accounts().end());

for (const auto& [key, acc] : ordered)
for (const auto& [key, acc] : s)

Check warning on line 98 in test/blockchaintest/blockchaintest_runner.cpp

View check run for this annotation

Codecov / codecov/patch

test/blockchaintest/blockchaintest_runner.cpp#L98

Added line #L98 was not covered by tests
{
out << key << " : \n";
out << "\tnonce : " << acc.nonce << "\n";
Expand All @@ -106,15 +104,11 @@

if (!acc.storage.empty())
{
const std::map<bytes32, state::StorageValue> ordered_storage(
acc.storage.begin(), acc.storage.end());

out << "\tstorage : "
<< "\n";
for (const auto& [s_key, val] : ordered_storage)
out << "\tstorage : \n";
for (const auto& [s_key, val] : acc.storage)

Check warning on line 108 in test/blockchaintest/blockchaintest_runner.cpp

View check run for this annotation

Codecov / codecov/patch

test/blockchaintest/blockchaintest_runner.cpp#L107-L108

Added lines #L107 - L108 were not covered by tests
{
if (val.current) // Skip 0 values.
out << "\t\t" << s_key << " : " << hex0x(val.current) << "\n";
if (!is_zero(val)) // Skip 0 values.
out << "\t\t" << s_key << " : " << hex0x(val) << "\n";

Check warning on line 111 in test/blockchaintest/blockchaintest_runner.cpp

View check run for this annotation

Codecov / codecov/patch

test/blockchaintest/blockchaintest_runner.cpp#L110-L111

Added lines #L110 - L111 were not covered by tests
}
}
}
Expand All @@ -131,7 +125,7 @@
SCOPED_TRACE(std::string{evmc::to_string(c.rev.get_revision(0))} + '/' +
std::to_string(case_index) + '/' + c.name);

auto state = c.pre_state;
auto state = c.pre_state.to_intra_state();

const state::BlockInfo genesis{
.number = c.genesis_block_header.block_number,
Expand All @@ -151,8 +145,7 @@

const auto genesis_res = apply_block(state, vm, genesis, {}, c.rev.get_revision(0), {});

EXPECT_EQ(
state::mpt_hash(state.get_accounts()), state::mpt_hash(c.pre_state.get_accounts()));
EXPECT_EQ(state::mpt_hash(TestState{state}), state::mpt_hash(c.pre_state));

if (c.rev.get_revision(0) >= EVMC_SHANGHAI)
{
Expand Down Expand Up @@ -184,7 +177,7 @@
'/' + c.name + '/' + std::to_string(test_block.block_info.number));

EXPECT_EQ(
state::mpt_hash(state.get_accounts()), test_block.expected_block_header.state_root);
state::mpt_hash(TestState{state}), test_block.expected_block_header.state_root);

if (rev >= EVMC_SHANGHAI)
{
Expand All @@ -203,16 +196,17 @@
// TODO: Add difficulty calculation verification.
}

const auto post_state_hash =
std::holds_alternative<state::State>(c.expectation.post_state) ?
state::mpt_hash(std::get<state::State>(c.expectation.post_state).get_accounts()) :
const TestState post{state};
const auto expected_post_hash =
std::holds_alternative<TestState>(c.expectation.post_state) ?
state::mpt_hash(std::get<TestState>(c.expectation.post_state)) :
std::get<hash256>(c.expectation.post_state);
EXPECT_TRUE(state::mpt_hash(state.get_accounts()) == post_state_hash)
EXPECT_TRUE(state::mpt_hash(post) == expected_post_hash)
<< "Result state:\n"
<< print_state(state)
<< (std::holds_alternative<state::State>(c.expectation.post_state) ?
<< print_state(post)
<< (std::holds_alternative<TestState>(c.expectation.post_state) ?

Check warning on line 207 in test/blockchaintest/blockchaintest_runner.cpp

View check run for this annotation

Codecov / codecov/patch

test/blockchaintest/blockchaintest_runner.cpp#L206-L207

Added lines #L206 - L207 were not covered by tests
"\n\nExpected state:\n" +
print_state(std::get<state::State>(c.expectation.post_state)) :
print_state(std::get<TestState>(c.expectation.post_state)) :

Check warning on line 209 in test/blockchaintest/blockchaintest_runner.cpp

View check run for this annotation

Codecov / codecov/patch

test/blockchaintest/blockchaintest_runner.cpp#L209

Added line #L209 was not covered by tests
"");
}
}
Expand Down
2 changes: 2 additions & 0 deletions test/state/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ target_sources(
rlp.hpp
state.hpp
state.cpp
test_state.hpp
test_state.cpp
)

option(EVMONE_PRECOMPILES_SILKPRE "Enable precompiles support via silkpre library" OFF)
Expand Down
12 changes: 6 additions & 6 deletions test/state/mpt_hash.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,31 @@
// SPDX-License-Identifier: Apache-2.0

#include "mpt_hash.hpp"
#include "account.hpp"
#include "mpt.hpp"
#include "rlp.hpp"
#include "state.hpp"
#include "test_state.hpp"

namespace evmone::state
{
namespace
{
hash256 mpt_hash(const std::unordered_map<hash256, StorageValue>& storage)
hash256 mpt_hash(const std::map<bytes32, bytes32>& storage)
{
MPT trie;
for (const auto& [key, value] : storage)
{
if (!is_zero(value.current)) // Skip "deleted" values.
trie.insert(keccak256(key), rlp::encode(rlp::trim(value.current)));
if (!is_zero(value)) // Skip "deleted" values.
trie.insert(keccak256(key), rlp::encode(rlp::trim(value)));
}
return trie.hash();
}
} // namespace

hash256 mpt_hash(const std::unordered_map<address, Account>& accounts)
hash256 mpt_hash(const test::TestState& state)
{
MPT trie;
for (const auto& [addr, acc] : accounts)
for (const auto& [addr, acc] : state)
{
trie.insert(keccak256(addr),
rlp::encode_tuple(acc.nonce, acc.balance, mpt_hash(acc.storage), keccak256(acc.code)));
Expand Down
9 changes: 6 additions & 3 deletions test/state/mpt_hash.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@
#include <span>
#include <unordered_map>

namespace evmone::state
namespace evmone::test
{
struct Account;
class TestState;
}

namespace evmone::state
{
/// Computes Merkle Patricia Trie root hash for the given collection of state accounts.
hash256 mpt_hash(const std::unordered_map<address, Account>& accounts);
hash256 mpt_hash(const test::TestState& state);

/// Computes Merkle Patricia Trie root hash for the given list of structures.
template <typename T>
Expand Down
7 changes: 5 additions & 2 deletions test/state/state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
namespace evmone::state
{
/// The Ethereum State: the collection of accounts mapped by their addresses.
///
/// TODO: This class is copyable for testing. Consider making it non-copyable.
class State
{
struct JournalBase
Expand Down Expand Up @@ -71,6 +69,11 @@ class State
std::vector<JournalEntry> m_journal;

public:
State() = default;
State(const State&) = delete;
State(State&&) = default;
State& operator=(State&&) = default;

/// Inserts the new account at the address.
/// There must not exist any account under this address before.
Account& insert(const address& addr, Account account = {});
Expand Down
34 changes: 34 additions & 0 deletions test/state/test_state.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2024 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0
#include "test_state.hpp"
#include "state.hpp"

namespace evmone::test
{
TestState::TestState(const state::State& intra_state)
{
for (const auto& [addr, acc] : intra_state.get_accounts())
{
auto& test_acc =
(*this)[addr] = {.nonce = acc.nonce, .balance = acc.balance, .code = acc.code};
auto& test_storage = test_acc.storage;
for (const auto& [key, value] : acc.storage)
test_storage[key] = value.current;
}
}

state::State TestState::to_intra_state() const
{
state::State intra_state;
for (const auto& [addr, acc] : *this)
{
auto& intra_acc = intra_state.insert(
addr, {.nonce = acc.nonce, .balance = acc.balance, .code = acc.code});
auto& storage = intra_acc.storage;
for (const auto& [key, value] : acc.storage)
storage[key] = {.current = value, .original = value};
}
return intra_state;
}
} // namespace evmone::test
67 changes: 67 additions & 0 deletions test/state/test_state.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2024 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0
#pragma once

#include <evmc/evmc.hpp>
#include <intx/intx.hpp>
#include <map>

namespace evmone
{
namespace state
{
class State;
}

namespace test
{
using evmc::address;
using evmc::bytes;
using evmc::bytes32;
using intx::uint256;

/// Ethereum account representation for tests.
struct TestAccount

Check warning on line 25 in test/state/test_state.hpp

View check run for this annotation

Codecov / codecov/patch

test/state/test_state.hpp#L25

Added line #L25 was not covered by tests
{
uint64_t nonce = 0;
uint256 balance;
std::map<bytes32, bytes32> storage;
bytes code;

bool operator==(const TestAccount&) const noexcept = default;
};

/// Ethereum State representation for tests.
///
/// This is a simplified variant of state::State:
/// it hides some details related to transaction execution (e.g. original storage values)
/// and is also easier to work with in tests.
class TestState : public std::map<address, TestAccount>
{
public:
using map::map;

Check warning on line 43 in test/state/test_state.hpp

View check run for this annotation

Codecov / codecov/patch

test/state/test_state.hpp#L43

Added line #L43 was not covered by tests

/// Inserts new account to the state.
///
/// This method is for compatibility with state::State::insert().
/// Don't use it in new tests, use std::map interface instead.
/// TODO: deprecate this method.
void insert(const address& addr, TestAccount&& acc) { (*this)[addr] = std::move(acc); }

/// Gets the reference to an existing account.
///
/// This method is for compatibility with state::State::get().
/// Don't use it in new tests, use std::map interface instead.
/// TODO: deprecate this method.
TestAccount& get(const address& addr) { return (*this)[addr]; }

/// Converts the intra state to TestState.
explicit TestState(const state::State& intra_state);

/// Converts the TestState to intra state for transaction execution.
[[nodiscard]] state::State to_intra_state() const;
};

} // namespace test
} // namespace evmone
9 changes: 5 additions & 4 deletions test/statetest/statetest.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#pragma once

#include "../state/state.hpp"
chfast marked this conversation as resolved.
Show resolved Hide resolved
#include "../state/test_state.hpp"
#include <nlohmann/json.hpp>

namespace json = nlohmann;
Expand Down Expand Up @@ -54,7 +55,7 @@ struct StateTransitionTest
};

std::string name;
state::State pre_state;
TestState pre_state;
state::BlockInfo block;
TestMultiTransaction multi_tx;
std::vector<Case> cases;
Expand Down Expand Up @@ -86,21 +87,21 @@ template <>
state::Withdrawal from_json<state::Withdrawal>(const json::json& j);

template <>
state::State from_json<state::State>(const json::json& j);
TestState from_json<TestState>(const json::json& j);

template <>
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 std::unordered_map<address, state::Account>& accounts);
json::json to_json(const TestState& state);

std::vector<StateTransitionTest> load_state_tests(std::istream& input);

/// Validates an Ethereum state:
/// - checks that there are no zero-value storage entries,
/// - checks that there are no invalid EOF codes.
/// Throws std::invalid_argument exception.
void validate_state(const state::State& state, evmc_revision rev);
void validate_state(const TestState& state, evmc_revision rev);

/// Execute the state @p test using the @p vm.
///
Expand Down
Loading