Skip to content

Commit

Permalink
Merge pull request #589 from ethereum/test_state_transition
Browse files Browse the repository at this point in the history
test: Declarative state transition test suite
  • Loading branch information
chfast authored Apr 13, 2023
2 parents 7028b6b + 9926f46 commit 1fb6fa6
Show file tree
Hide file tree
Showing 12 changed files with 255 additions and 8 deletions.
1 change: 1 addition & 0 deletions test/state/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ target_sources(
bloom_filter.cpp
errors.hpp
hash_utils.hpp
hash_utils.cpp
host.hpp
host.cpp
mpt.hpp
Expand Down
14 changes: 14 additions & 0 deletions test/state/hash_utils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2023 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0
#include "hash_utils.hpp"

std::ostream& operator<<(std::ostream& out, const evmone::address& a)
{
return out << "0x" << hex(a);
}

std::ostream& operator<<(std::ostream& out, const evmone::bytes32& b)
{
return out << "0x" << hex(b);
}
3 changes: 3 additions & 0 deletions test/state/hash_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@ inline hash256 keccak256(bytes_view data) noexcept
return h;
}
} // namespace evmone

std::ostream& operator<<(std::ostream& out, const evmone::address& a);
std::ostream& operator<<(std::ostream& out, const evmone::bytes32& b);
4 changes: 4 additions & 0 deletions test/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ target_sources(
state_mpt_test.cpp
state_new_account_address_test.cpp
state_rlp_test.cpp
state_transition.hpp
state_transition.cpp
state_transition_create_test.cpp
state_transition_eof_test.cpp
statetest_loader_block_info_test.cpp
statetest_loader_test.cpp
statetest_loader_tx_test.cpp
Expand Down
4 changes: 2 additions & 2 deletions test/unittests/evm_control_flow_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ TEST_P(evm, jump_to_block_beginning)

TEST_P(evm, jumpi_stack)
{
const auto code = push(0xde) + jumpi(6, OP_CALLDATASIZE) + OP_JUMPDEST + ret_top();
const auto code = push(0xde) + jumpi(6, calldatasize()) + OP_JUMPDEST + ret_top();
execute(code);
EXPECT_OUTPUT_INT(0xde);
execute(code, "ee"_hex);
Expand Down Expand Up @@ -158,7 +158,7 @@ TEST_P(evm, pc_after_jump_1)

TEST_P(evm, pc_after_jump_2)
{
const auto code = OP_CALLDATASIZE + push(9) + OP_JUMPI + push(12) + OP_PC + OP_SWAP1 + OP_JUMP +
const auto code = calldatasize() + push(9) + OP_JUMPI + push(12) + OP_PC + OP_SWAP1 + OP_JUMP +
OP_JUMPDEST + OP_GAS + OP_PC + OP_JUMPDEST + ret_top();

execute(code);
Expand Down
6 changes: 2 additions & 4 deletions test/unittests/evm_memory_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,9 @@ TEST_P(evm, keccak256_memory_cost)

TEST_P(evm, calldatacopy_memory_cost)
{
auto code = push(1) + push(0) + push(0) + OP_CALLDATACOPY;
const auto code = calldatacopy(0, 0, 1);
execute(18, code);
EXPECT_EQ(result.status_code, EVMC_SUCCESS);
execute(17, code);
EXPECT_EQ(result.status_code, EVMC_OUT_OF_GAS);
EXPECT_GAS_USED(EVMC_SUCCESS, 18);
}


Expand Down
4 changes: 2 additions & 2 deletions test/unittests/evm_state_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -585,8 +585,8 @@ TEST_P(evm, extcodecopy_fill_tail)

TEST_P(evm, extcodecopy_buffer_overflow)
{
const auto code = bytecode{} + OP_NUMBER + OP_TIMESTAMP + OP_CALLDATASIZE + OP_ADDRESS +
OP_EXTCODECOPY + ret(OP_CALLDATASIZE, OP_NUMBER);
const auto code = bytecode{} + OP_NUMBER + OP_TIMESTAMP + calldatasize() + OP_ADDRESS +
OP_EXTCODECOPY + ret(calldatasize(), OP_NUMBER);

host.accounts[msg.recipient].code = code;

Expand Down
73 changes: 73 additions & 0 deletions test/unittests/state_transition.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2023 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0

#include "state_transition.hpp"

namespace evmone::test
{
void state_transition::SetUp()
{
pre.insert(tx.sender, {.nonce = 1, .balance = tx.gas_limit * tx.max_gas_price + tx.value + 1});

// Default expectations.
expect.post[Coinbase].exists = true;
expect.post[tx.sender].exists = true;
}

void state_transition::TearDown()
{
auto& state = pre;
const auto res = evmone::state::transition(state, block, tx, rev, vm);
ASSERT_TRUE(holds_alternative<TransactionReceipt>(res))
<< std::get<std::error_code>(res).message();
const auto& receipt = std::get<TransactionReceipt>(res);

EXPECT_EQ(receipt.status, expect.status);
if (expect.gas_used.has_value())
{
EXPECT_EQ(receipt.gas_used, *expect.gas_used);
}

for (const auto& [addr, expected_acc] : expect.post)
{
const auto acc = state.find(addr);
if (!expected_acc.exists)
{
EXPECT_EQ(acc, nullptr) << "account " << addr << " should not exist";
}
else
{
ASSERT_NE(acc, nullptr) << "account " << addr << " should exist";
if (expected_acc.nonce.has_value())
{
EXPECT_EQ(acc->nonce, *expected_acc.nonce);
}
if (expected_acc.balance.has_value())
{
EXPECT_EQ(acc->balance, *expected_acc.balance)
<< to_string(acc->balance) << " vs " << to_string(*expected_acc.balance);
}
if (expected_acc.code.has_value())
{
EXPECT_EQ(acc->code, *expected_acc.code);
}
for (const auto& [key, value] : expected_acc.storage)
{
EXPECT_EQ(acc->storage[key].current, value);
}
for (const auto& [key, value] : acc->storage)
{
// Find unexpected storage keys. This will also report entries with value 0.
EXPECT_TRUE(expected_acc.storage.contains(key))
<< "unexpected storage key " << key << "=" << value.current << " in " << addr;
}
}
}

for (const auto& [addr, _] : state.get_accounts())
{
EXPECT_TRUE(expect.post.contains(addr)) << "unexpected account " << addr;
}
}
} // namespace evmone::test
74 changes: 74 additions & 0 deletions test/unittests/state_transition.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2023 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0
#pragma once

#include <evmone/evmone.h>
#include <gtest/gtest.h>
#include <test/state/host.hpp>

#pragma GCC diagnostic ignored "-Wmissing-field-initializers"

namespace evmone::test
{
using namespace evmone;
using namespace evmone::state;

/// Fixture to defining test cases in form similar to JSON State Tests.
///
/// It takes the "pre" state and produces "post" state by applying the defined "tx" transaction.
/// Then expectations declared in "except" are checked in the "post" state.
class state_transition : public testing::Test
{
protected:
/// The default sender address of the test transaction.
/// Private key: 0xa45355879.
static constexpr auto Sender = 0xe100DeB58f38F7fd62d14E37e2Fe9ce019Aa001e_address;

/// The default destination address of the test transaction.
static constexpr auto To = 0xc0de_address;

static constexpr auto Coinbase = 0xc014bace_address;

static inline evmc::VM vm{evmc_create_evmone()};

struct ExpectedAccount
{
bool exists = true;
std::optional<uint64_t> nonce;
std::optional<intx::uint256> balance;
std::optional<bytes> code;
std::unordered_map<bytes32, bytes32> storage;
};

struct Expectation
{
evmc_status_code status = EVMC_SUCCESS;
std::optional<int64_t> gas_used;

std::unordered_map<address, ExpectedAccount> post;
};


evmc_revision rev = EVMC_SHANGHAI;
BlockInfo block{
.gas_limit = 1'000'000,
.coinbase = Coinbase,
.base_fee = 999,
};
Transaction tx{
.gas_limit = block.gas_limit,
.max_gas_price = block.base_fee + 1,
.max_priority_gas_price = 1,
.sender = Sender,
};
State pre;
Expectation expect;

void SetUp() override;

/// The test runner.
void TearDown() override;
};

} // namespace evmone::test
34 changes: 34 additions & 0 deletions test/unittests/state_transition_create_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2023 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0

#include "../utils/bytecode.hpp"
#include "state_transition.hpp"

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

TEST_F(state_transition, create2_factory)
{
static constexpr auto create_address = 0xfd8e7707356349027a32d71eabc7cb0cf9d7cbb4_address;

const auto factory_code =
calldatacopy(0, 0, calldatasize()) + create2().input(0, calldatasize());
const auto initcode = mstore8(0, push(0xFE)) + ret(0, 1);

tx.to = To;
tx.data = initcode;
pre.insert(*tx.to, {.nonce = 1, .code = factory_code});

expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; // CREATE caller's nonce must be bumped
expect.post[create_address].code = bytes{0xFE};
}

TEST_F(state_transition, create_tx)
{
static constexpr auto create_address = 0x8ef300b6a6a0b41e4f5d717074d9fd5c605c7285_address;

tx.data = mstore8(0, push(0xFE)) + ret(0, 1);

expect.post[create_address].code = bytes{0xFE};
}
36 changes: 36 additions & 0 deletions test/unittests/state_transition_eof_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2023 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0

#include "../utils/bytecode.hpp"
#include "state_transition.hpp"

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

TEST_F(state_transition, eof_invalid_initcode)
{
// TODO: Correction of this address is not verified.
static constexpr auto create_address = 0x864bbda5c698ac34b47a9ea3bd4228802cc5ce3b_address;

rev = EVMC_CANCUN;
tx.to = To;
pre.insert(*tx.to,
{
.nonce = 1,
.storage = {{0x01_bytes32, {.current = 0x01_bytes32, .original = 0x01_bytes32}}},
.code = eof1_bytecode(create() + push(1) + OP_SSTORE + OP_STOP, 3),
});

EXPECT_EQ(pre.get(tx.sender).balance, 1'000'000'001); // Fixture sanity check.

expect.gas_used = 985407;

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);
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;
}
10 changes: 10 additions & 0 deletions test/utils/bytecode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,16 @@ inline bytecode calldataload(bytecode index)
return index + OP_CALLDATALOAD;
}

inline bytecode calldatasize()
{
return OP_CALLDATASIZE;
}

inline bytecode calldatacopy(bytecode src, bytecode dst, bytecode size)
{
return std::move(size) + std::move(dst) + std::move(src) + OP_CALLDATACOPY;
}

inline bytecode sstore(bytecode index, bytecode value)
{
return value + index + OP_SSTORE;
Expand Down

0 comments on commit 1fb6fa6

Please sign in to comment.