Skip to content

Commit b1c9637

Browse files
authored
Implement EIP-4788: Beacon block root in the EVM (#709)
Implement the system call from the "Block processing" of the EIP-4788. https://eips.ethereum.org/EIPS/eip-4788#block-processing At the beginning of each block a gas-free call is invoked from the _system_ account (`0xff..fe`) to the specific _system_ contract.
2 parents 2fb7cc1 + 87e8d9b commit b1c9637

9 files changed

+91
-1
lines changed

test/blockchaintest/blockchaintest.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ struct BlockHeader
3535
hash256 hash;
3636
hash256 transactions_root;
3737
hash256 withdrawal_root;
38+
hash256 parent_beacon_block_root;
3839
};
3940

4041
struct TestBlock

test/blockchaintest/blockchaintest_loader.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ BlockHeader from_json<BlockHeader>(const json::json& j)
4040
.hash = from_json<hash256>(j.at("hash")),
4141
.transactions_root = from_json<hash256>(j.at("transactionsTrie")),
4242
.withdrawal_root = load_if_exists<hash256>(j, "withdrawalsRoot"),
43+
.parent_beacon_block_root = load_if_exists<hash256>(j, "parentBeaconBlockRoot"),
4344
};
4445
}
4546

@@ -58,6 +59,7 @@ static TestBlock load_test_block(const json::json& j, evmc_revision rev)
5859
tb.block_info.difficulty = tb.expected_block_header.difficulty;
5960
tb.block_info.prev_randao = tb.expected_block_header.prev_randao;
6061
tb.block_info.base_fee = tb.expected_block_header.base_fee_per_gas;
62+
tb.block_info.parent_beacon_block_root = tb.expected_block_header.parent_beacon_block_root;
6163

6264
// Override prev_randao with difficulty pre-Merge
6365
if (rev < EVMC_PARIS)

test/blockchaintest/blockchaintest_runner.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ TransitionResult apply_block(state::State& state, evmc::VM& vm, const state::Blo
3333
const std::vector<state::Transaction>& txs, evmc_revision rev,
3434
std::optional<int64_t> block_reward)
3535
{
36+
state::system_call(state, block, rev, vm);
37+
3638
std::vector<state::Log> txs_logs;
3739
int64_t block_gas_left = block.gas_limit;
3840

test/state/host.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,8 @@ evmc::Result Host::call(const evmc_message& orig_msg) noexcept
319319
evmc_tx_context Host::get_tx_context() const noexcept
320320
{
321321
// TODO: The effective gas price is already computed in transaction validation.
322-
assert(m_tx.max_gas_price >= m_block.base_fee);
322+
// TODO: The effective gas price calculation is broken for system calls (gas price 0).
323+
assert(m_tx.max_gas_price >= m_block.base_fee || m_tx.max_gas_price == 0);
323324
const auto priority_gas_price =
324325
std::min(m_tx.max_priority_gas_price, m_tx.max_gas_price - m_block.base_fee);
325326
const auto effective_gas_price = m_block.base_fee + priority_gas_price;

test/state/state.cpp

+35
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,41 @@ void delete_empty_accounts(State& state)
147147
}
148148
} // namespace
149149

150+
void system_call(State& state, const BlockInfo& block, evmc_revision rev, evmc::VM& vm)
151+
{
152+
static constexpr auto SystemAddress = 0xfffffffffffffffffffffffffffffffffffffffe_address;
153+
static constexpr auto BeaconRootsAddress = 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02_address;
154+
155+
if (rev >= EVMC_CANCUN)
156+
{
157+
if (const auto acc = state.find(BeaconRootsAddress); acc != nullptr)
158+
{
159+
const evmc_message msg{
160+
.kind = EVMC_CALL,
161+
.gas = 30'000'000,
162+
.recipient = BeaconRootsAddress,
163+
.sender = SystemAddress,
164+
.input_data = block.parent_beacon_block_root.bytes,
165+
.input_size = sizeof(block.parent_beacon_block_root),
166+
};
167+
168+
const Transaction empty_tx{};
169+
Host host{rev, vm, state, block, empty_tx};
170+
const auto& code = acc->code;
171+
[[maybe_unused]] const auto res = vm.execute(host, rev, msg, code.data(), code.size());
172+
assert(res.status_code == EVMC_SUCCESS);
173+
assert(acc->access_status == EVMC_ACCESS_COLD);
174+
175+
// Reset storage status.
176+
for (auto& [_, val] : acc->storage)
177+
{
178+
val.access_status = EVMC_ACCESS_COLD;
179+
val.original = val.current;
180+
}
181+
}
182+
}
183+
}
184+
150185
void finalize(State& state, evmc_revision rev, const address& coinbase,
151186
std::optional<uint64_t> block_reward, std::span<const Ommer> ommers,
152187
std::span<const Withdrawal> withdrawals)

test/state/state.hpp

+7
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ struct BlockInfo
9797
int64_t parent_difficulty = 0;
9898
hash256 parent_ommers_hash;
9999
bytes32 prev_randao;
100+
hash256 parent_beacon_block_root;
100101
uint64_t base_fee = 0;
101102
std::vector<Ommer> ommers;
102103
std::vector<Withdrawal> withdrawals;
@@ -190,6 +191,12 @@ std::variant<int64_t, std::error_code> validate_transaction(const Account& sende
190191
const BlockInfo& block, const Transaction& tx, evmc_revision rev,
191192
int64_t block_gas_left) noexcept;
192193

194+
/// Performs the system call.
195+
///
196+
/// Executes code at pre-defined accounts from the system sender (0xff...fe).
197+
/// The sender's nonce is not increased.
198+
void system_call(State& state, const BlockInfo& block, evmc_revision rev, evmc::VM& vm);
199+
193200
/// Defines how to RLP-encode a Transaction.
194201
[[nodiscard]] bytes rlp_encode(const Transaction& tx);
195202

test/statetest/statetest_loader.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ state::BlockInfo from_json<state::BlockInfo>(const json::json& j)
231231
.parent_difficulty = parent_difficulty,
232232
.parent_ommers_hash = parent_uncle_hash,
233233
.prev_randao = prev_randao,
234+
.parent_beacon_block_root = {},
234235
.base_fee = base_fee,
235236
.ommers = std::move(ommers),
236237
.withdrawals = std::move(withdrawals),

test/unittests/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ target_sources(
4646
state_new_account_address_test.cpp
4747
state_precompiles_test.cpp
4848
state_rlp_test.cpp
49+
state_system_call_test.cpp
4950
state_transition.hpp
5051
state_transition.cpp
5152
state_transition_block_test.cpp
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// evmone: Fast Ethereum Virtual Machine implementation
2+
// Copyright 2023 The evmone Authors.
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
#include <evmone/evmone.h>
6+
#include <gtest/gtest.h>
7+
#include <test/state/state.hpp>
8+
#include <test/utils/bytecode.hpp>
9+
10+
using namespace evmc;
11+
using namespace evmone::state;
12+
13+
TEST(state_system_call, non_existient)
14+
{
15+
evmc::VM vm;
16+
State state;
17+
18+
system_call(state, {}, EVMC_CANCUN, vm);
19+
20+
EXPECT_EQ(state.get_accounts().size(), 0);
21+
}
22+
23+
TEST(state_system_call, sstore_timestamp)
24+
{
25+
static constexpr auto BeaconRootsAddress = 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02_address;
26+
27+
evmc::VM vm{evmc_create_evmone()};
28+
const BlockInfo block{.number = 1, .timestamp = 404};
29+
State state;
30+
state.insert(BeaconRootsAddress, {.code = sstore(OP_NUMBER, OP_TIMESTAMP)});
31+
32+
system_call(state, block, EVMC_CANCUN, vm);
33+
34+
ASSERT_EQ(state.get_accounts().size(), 1);
35+
EXPECT_EQ(state.get(BeaconRootsAddress).nonce, 0);
36+
EXPECT_EQ(state.get(BeaconRootsAddress).balance, 0);
37+
const auto& storage = state.get(BeaconRootsAddress).storage;
38+
ASSERT_EQ(storage.size(), 1);
39+
EXPECT_EQ(storage.at(0x01_bytes32).current, 404);
40+
}

0 commit comments

Comments
 (0)