diff --git a/test/blockchaintest/blockchaintest_runner.cpp b/test/blockchaintest/blockchaintest_runner.cpp index c8d37448a9..b80f83e3b4 100644 --- a/test/blockchaintest/blockchaintest_runner.cpp +++ b/test/blockchaintest/blockchaintest_runner.cpp @@ -5,6 +5,7 @@ #include "../state/mpt_hash.hpp" #include "../state/rlp.hpp" #include "../state/state.hpp" +#include "../state/system_contracts.hpp" #include "../test/statetest/statetest.hpp" #include "blockchaintest.hpp" #include diff --git a/test/state/CMakeLists.txt b/test/state/CMakeLists.txt index db82138aa1..533506506a 100644 --- a/test/state/CMakeLists.txt +++ b/test/state/CMakeLists.txt @@ -30,6 +30,8 @@ target_sources( rlp.hpp state.hpp state.cpp + system_contracts.hpp + system_contracts.cpp test_state.hpp test_state.cpp ) diff --git a/test/state/state.cpp b/test/state/state.cpp index cdf159739d..09a426aab0 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -379,40 +379,6 @@ void delete_empty_accounts(State& state) } } // namespace -void system_call(State& state, const BlockInfo& block, evmc_revision rev, evmc::VM& vm) -{ - static constexpr auto SystemAddress = 0xfffffffffffffffffffffffffffffffffffffffe_address; - static constexpr auto BeaconRootsAddress = 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02_address; - - if (rev >= EVMC_CANCUN) - { - if (const auto acc = state.find(BeaconRootsAddress); acc != nullptr) - { - const evmc_message msg{ - .kind = EVMC_CALL, - .gas = 30'000'000, - .recipient = BeaconRootsAddress, - .sender = SystemAddress, - .input_data = block.parent_beacon_block_root.bytes, - .input_size = sizeof(block.parent_beacon_block_root), - }; - - const Transaction empty_tx{}; - Host host{rev, vm, state, block, empty_tx}; - const auto& code = acc->code; - [[maybe_unused]] const auto res = vm.execute(host, rev, msg, code.data(), code.size()); - assert(res.status_code == EVMC_SUCCESS); - assert(acc->access_status == EVMC_ACCESS_COLD); - - // Reset storage status. - for (auto& [_, val] : acc->storage) - { - val.access_status = EVMC_ACCESS_COLD; - val.original = val.current; - } - } - } -} void finalize(State& state, evmc_revision rev, const address& coinbase, std::optional block_reward, std::span ommers, diff --git a/test/state/state.hpp b/test/state/state.hpp index c707fcddf0..d6469b11e3 100644 --- a/test/state/state.hpp +++ b/test/state/state.hpp @@ -279,12 +279,6 @@ std::variant validate_transaction(const Account& sende const BlockInfo& block, const Transaction& tx, evmc_revision rev, int64_t block_gas_left, int64_t blob_gas_left) noexcept; -/// Performs the system call. -/// -/// Executes code at pre-defined accounts from the system sender (0xff...fe). -/// The sender's nonce is not increased. -void system_call(State& state, const BlockInfo& block, evmc_revision rev, evmc::VM& vm); - /// Defines how to RLP-encode a Transaction. [[nodiscard]] bytes rlp_encode(const Transaction& tx); diff --git a/test/state/system_contracts.cpp b/test/state/system_contracts.cpp new file mode 100644 index 0000000000..d4e1c52acd --- /dev/null +++ b/test/state/system_contracts.cpp @@ -0,0 +1,74 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2023 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include "system_contracts.hpp" +#include "host.hpp" +#include "state.hpp" + +namespace evmone::state +{ +namespace +{ +/// Information about a registered system contract. +struct SystemContract +{ + using GetInputFn = bytes_view(const BlockInfo&) noexcept; + + evmc_revision since = EVMC_MAX_REVISION; ///< EVM revision in which added. + address addr; ///< Address of the system contract. + GetInputFn* get_input = nullptr; ///< How to get the input for the system call. +}; + +/// Registered system contracts. +constexpr std::array SYSTEM_CONTRACTS{ + SystemContract{EVMC_CANCUN, BEACON_ROOTS_ADDRESS, + [](const BlockInfo& block) noexcept { return bytes_view{block.parent_beacon_block_root}; }}, +}; + +static_assert(std::ranges::is_sorted(SYSTEM_CONTRACTS, + [](const auto& a, const auto& b) noexcept { return a.since < b.since; }), + "system contract entries must be ordered by revision"); + +} // namespace + +void system_call(State& state, const BlockInfo& block, evmc_revision rev, evmc::VM& vm) +{ + for (const auto& [since, addr, get_input] : SYSTEM_CONTRACTS) + { + if (rev < since) + return; // Because entries are ordered, there are no other contracts for this revision. + + // Skip the call if the target account doesn't exist. This is by EIP-4788 spec. + // > if no code exists at [address], the call must fail silently. + const auto acc = state.find(addr); + if (acc == nullptr) + continue; + + const auto input = get_input(block); + + const evmc_message msg{ + .kind = EVMC_CALL, + .gas = 30'000'000, + .recipient = addr, + .sender = SYSTEM_ADDRESS, + .input_data = input.data(), + .input_size = input.size(), + }; + + const Transaction empty_tx{}; + Host host{rev, vm, state, block, empty_tx}; + const auto& code = acc->code; + [[maybe_unused]] const auto res = vm.execute(host, rev, msg, code.data(), code.size()); + assert(res.status_code == EVMC_SUCCESS); + assert(acc->access_status == EVMC_ACCESS_COLD); + + // Reset storage status. + for (auto& [_, val] : acc->storage) + { + val.access_status = EVMC_ACCESS_COLD; + val.original = val.current; + } + } +} +} // namespace evmone::state diff --git a/test/state/system_contracts.hpp b/test/state/system_contracts.hpp new file mode 100644 index 0000000000..7423661335 --- /dev/null +++ b/test/state/system_contracts.hpp @@ -0,0 +1,26 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2023 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +namespace evmone::state +{ +using namespace evmc::literals; + +/// The address of the sender of the system calls (EIP-4788). +constexpr auto SYSTEM_ADDRESS = 0xfffffffffffffffffffffffffffffffffffffffe_address; + +/// The address of the system contract storing the root hashes of beacon chain blocks (EIP-4788). +constexpr auto BEACON_ROOTS_ADDRESS = 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02_address; + +struct BlockInfo; +class State; + +/// Performs the system call: invokes system contracts. +/// +/// Executes code of pre-defined accounts via pseudo-transaction from the system sender (0xff...fe). +/// The sender's nonce is not increased. +void system_call(State& state, const BlockInfo& block, evmc_revision rev, evmc::VM& vm); +} // namespace evmone::state diff --git a/test/t8n/t8n.cpp b/test/t8n/t8n.cpp index 926245a465..316ec57d10 100644 --- a/test/t8n/t8n.cpp +++ b/test/t8n/t8n.cpp @@ -6,6 +6,7 @@ #include "../state/ethash_difficulty.hpp" #include "../state/mpt_hash.hpp" #include "../state/rlp.hpp" +#include "../state/system_contracts.hpp" #include "../statetest/statetest.hpp" #include "../utils/utils.hpp" #include diff --git a/test/unittests/state_system_call_test.cpp b/test/unittests/state_system_call_test.cpp index 08847f2d9f..85f1151280 100644 --- a/test/unittests/state_system_call_test.cpp +++ b/test/unittests/state_system_call_test.cpp @@ -5,37 +5,42 @@ #include #include #include +#include #include using namespace evmc::literals; using namespace evmone::state; using namespace evmone::test; -TEST(state_system_call, non_existient) +class state_system_call : public testing::Test { - evmc::VM vm; +protected: + evmc::VM vm{evmc_create_evmone()}; State state; +}; - system_call(state, {}, EVMC_CANCUN, vm); +TEST_F(state_system_call, non_existient) +{ + // Use MAX revision to invoke all activate system contracts. + system_call(state, {}, EVMC_MAX_REVISION, vm); - EXPECT_EQ(state.get_accounts().size(), 0); + EXPECT_EQ(state.get_accounts().size(), 0) << "State must remain unchanged"; } -TEST(state_system_call, sstore_timestamp) +TEST_F(state_system_call, beacon_roots) { - static constexpr auto BeaconRootsAddress = 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02_address; - - evmc::VM vm{evmc_create_evmone()}; - const BlockInfo block{.number = 1, .timestamp = 0x0404}; - State state; - state.insert(BeaconRootsAddress, {.code = sstore(OP_NUMBER, OP_TIMESTAMP)}); + const BlockInfo block{.number = 1, .parent_beacon_block_root = 0xbeac04004a54_bytes32}; + state.insert( + BEACON_ROOTS_ADDRESS, {.code = sstore(OP_NUMBER, calldataload(0)) + sstore(0, OP_CALLER)}); system_call(state, block, EVMC_CANCUN, vm); ASSERT_EQ(state.get_accounts().size(), 1); - EXPECT_EQ(state.get(BeaconRootsAddress).nonce, 0); - EXPECT_EQ(state.get(BeaconRootsAddress).balance, 0); - const auto& storage = state.get(BeaconRootsAddress).storage; - ASSERT_EQ(storage.size(), 1); - EXPECT_EQ(storage.at(0x01_bytes32).current, 0x0404_bytes32); + EXPECT_EQ(state.find(SYSTEM_ADDRESS), nullptr); + EXPECT_EQ(state.get(BEACON_ROOTS_ADDRESS).nonce, 0); + EXPECT_EQ(state.get(BEACON_ROOTS_ADDRESS).balance, 0); + const auto& storage = state.get(BEACON_ROOTS_ADDRESS).storage; + ASSERT_EQ(storage.size(), 2); + EXPECT_EQ(storage.at(0x01_bytes32).current, block.parent_beacon_block_root); + EXPECT_EQ(storage.at(0x00_bytes32).current, to_bytes32(SYSTEM_ADDRESS)); }