diff --git a/test/state/mpt_hash.cpp b/test/state/mpt_hash.cpp
index 316bd4cccf..910f768481 100644
--- a/test/state/mpt_hash.cpp
+++ b/test/state/mpt_hash.cpp
@@ -35,22 +35,18 @@ hash256 mpt_hash(const std::unordered_map
& accounts)
return trie.hash();
}
-hash256 mpt_hash(std::span transactions)
+template
+hash256 mpt_hash(std::span list)
{
MPT trie;
- for (size_t i = 0; i < transactions.size(); ++i)
- trie.insert(rlp::encode(i), rlp::encode(transactions[i]));
+ for (size_t i = 0; i < list.size(); ++i)
+ trie.insert(rlp::encode(i), rlp::encode(list[i]));
return trie.hash();
}
-hash256 mpt_hash(std::span receipts)
-{
- MPT trie;
- for (size_t i = 0; i < receipts.size(); ++i)
- trie.insert(rlp::encode(i), rlp::encode(receipts[i]));
-
- return trie.hash();
-}
+template hash256 mpt_hash(std::span);
+template hash256 mpt_hash(std::span);
+template hash256 mpt_hash(std::span);
} // namespace evmone::state
diff --git a/test/state/mpt_hash.hpp b/test/state/mpt_hash.hpp
index e0a25a9df6..58c7301d23 100644
--- a/test/state/mpt_hash.hpp
+++ b/test/state/mpt_hash.hpp
@@ -10,16 +10,20 @@
namespace evmone::state
{
struct Account;
-struct Transaction;
-struct TransactionReceipt;
/// Computes Merkle Patricia Trie root hash for the given collection of state accounts.
hash256 mpt_hash(const std::unordered_map& accounts);
-/// Computes Merkle Patricia Trie root hash for the given collection of transactions.
-hash256 mpt_hash(std::span transactions);
+/// Computes Merkle Patricia Trie root hash for the given list of structures.
+template
+hash256 mpt_hash(std::span list);
-/// Computes Merkle Patricia Trie root hash for the given collection of transactions receipts.
-hash256 mpt_hash(std::span receipts);
+/// A helper to automatically convert collections (e.g. vector, array) to span.
+template
+inline hash256 mpt_hash(const T& list)
+ requires std::is_convertible_v>
+{
+ return mpt_hash(std::span{list});
+}
} // namespace evmone::state
diff --git a/test/state/state.cpp b/test/state/state.cpp
index 150ba9214c..b461e6bcaa 100644
--- a/test/state/state.cpp
+++ b/test/state/state.cpp
@@ -295,4 +295,10 @@ std::variant transition(State& state, const
}
}
+[[nodiscard]] bytes rlp_encode(const Withdrawal& withdrawal)
+{
+ return rlp::encode_tuple(withdrawal.index, withdrawal.validator_index, withdrawal.recipient,
+ withdrawal.amount_in_gwei);
+}
+
} // namespace evmone::state
diff --git a/test/state/state.hpp b/test/state/state.hpp
index 76e5a347f6..28e11778e8 100644
--- a/test/state/state.hpp
+++ b/test/state/state.hpp
@@ -68,6 +68,8 @@ class State
struct Withdrawal
{
+ uint64_t index = 0;
+ uint64_t validator_index = 0;
address recipient;
uint64_t amount_in_gwei = 0; ///< The amount is denominated in gwei.
@@ -185,4 +187,7 @@ std::variant validate_transaction(const Account& sende
/// Defines how to RLP-encode a Log.
[[nodiscard]] bytes rlp_encode(const Log& log);
+/// Defines how to RLP-encode a Withdrawal.
+[[nodiscard]] bytes rlp_encode(const Withdrawal& withdrawal);
+
} // namespace evmone::state
diff --git a/test/statetest/statetest.hpp b/test/statetest/statetest.hpp
index a4cf8f54d4..da54feca6b 100644
--- a/test/statetest/statetest.hpp
+++ b/test/statetest/statetest.hpp
@@ -77,6 +77,9 @@ int64_t from_json(const json::json& j);
template <>
state::BlockInfo from_json(const json::json& j);
+template <>
+state::Withdrawal from_json(const json::json& j);
+
template <>
state::State from_json(const json::json& j);
diff --git a/test/statetest/statetest_loader.cpp b/test/statetest/statetest_loader.cpp
index 18a7c779b3..5b1938ba4d 100644
--- a/test/statetest/statetest_loader.cpp
+++ b/test/statetest/statetest_loader.cpp
@@ -147,6 +147,13 @@ inline uint64_t calculate_current_base_fee_eip1559(
return base_fee;
}
+template <>
+state::Withdrawal from_json(const json::json& j)
+{
+ return {from_json(j.at("index")), from_json(j.at("validatorIndex")),
+ from_json(j.at("address")), from_json(j.at("amount"))};
+}
+
template <>
state::BlockInfo from_json(const json::json& j)
{
@@ -175,10 +182,7 @@ state::BlockInfo from_json(const json::json& j)
if (const auto withdrawals_it = j.find("withdrawals"); withdrawals_it != j.end())
{
for (const auto& withdrawal : *withdrawals_it)
- {
- withdrawals.push_back({from_json(withdrawal.at("address")),
- from_json(withdrawal.at("amount"))});
- }
+ withdrawals.push_back(from_json(withdrawal));
}
std::unordered_map block_hashes;
@@ -238,7 +242,7 @@ evmc_revision to_rev(std::string_view s)
return EVMC_ISTANBUL;
if (s == "Berlin")
return EVMC_BERLIN;
- if (s == "London")
+ if (s == "London" || s == "ArrowGlacier")
return EVMC_LONDON;
if (s == "Merge")
return EVMC_PARIS;
diff --git a/test/t8n/t8n.cpp b/test/t8n/t8n.cpp
index 03e807a153..ebf946294a 100644
--- a/test/t8n/t8n.cpp
+++ b/test/t8n/t8n.cpp
@@ -197,6 +197,9 @@ int main(int argc, const char* argv[])
j_result["logsBloom"] = hex0x(compute_bloom_filter(receipts));
j_result["receiptsRoot"] = hex0x(state::mpt_hash(receipts));
+ if (rev >= EVMC_SHANGHAI)
+ j_result["withdrawalsRoot"] = hex0x(state::mpt_hash(block.withdrawals));
+
j_result["txRoot"] = hex0x(state::mpt_hash(transactions));
j_result["gasUsed"] = hex0x(cumulative_gas_used);
diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt
index aabc640ae0..cfd0dfe56d 100644
--- a/test/unittests/CMakeLists.txt
+++ b/test/unittests/CMakeLists.txt
@@ -48,6 +48,7 @@ target_sources(
statetest_loader_test.cpp
statetest_loader_tx_test.cpp
statetest_logs_hash_test.cpp
+ statetest_withdrawals_test.cpp
tracing_test.cpp
)
target_link_libraries(evmone-unittests PRIVATE evmone evmone::evmmax evmone::state evmone::statetestutils testutils evmc::instructions GTest::gtest GTest::gtest_main)
diff --git a/test/unittests/state_mpt_hash_test.cpp b/test/unittests/state_mpt_hash_test.cpp
index c44744b4b4..e9fa398ae2 100644
--- a/test/unittests/state_mpt_hash_test.cpp
+++ b/test/unittests/state_mpt_hash_test.cpp
@@ -279,3 +279,29 @@ TEST(state_mpt_hash, pre_byzantium_receipt)
EXPECT_EQ(mpt_hash(std::array{receipt0, receipt1, receipt2}),
0x8a4fa43a95939b06ad13ce8cd08e026ae6e79ea3c5fc80c732d252e2769ce778_bytes32);
}
+
+TEST(state_mpt_hash, mpt_hashing_test)
+{
+ // The same data as in 'statetest_withdrawals.withdrawals_warmup_test_case' unit test.
+ MPT trie;
+
+ trie.insert("80"_hex, "db808094c000000000000000000000000000000000000001830186a0"_hex);
+ EXPECT_EQ(
+ trie.hash(), 0x4ae14e53d6bf2a9c73891aef9d2e6373ff06d400b6e7727b17a5eceb5e8dec9d_bytes32);
+
+ trie.insert("01"_hex, "db018094c000000000000000000000000000000000000002830186a0"_hex);
+ EXPECT_EQ(
+ trie.hash(), 0xc5128234c0282b256e2aa91ddc783ddb2c21556766f2e11e64159561b59f8ac7_bytes32);
+
+ trie.insert("0x02"_hex, "0xdb028094c000000000000000000000000000000000000003830186a0"_hex);
+ trie.insert("0x03"_hex, "0xdb038094c000000000000000000000000000000000000004830186a0"_hex);
+ trie.insert("0x04"_hex, "0xdb048094c000000000000000000000000000000000000005830186a0"_hex);
+ trie.insert("0x05"_hex, "0xdb058094c000000000000000000000000000000000000006830186a0"_hex);
+ trie.insert("0x06"_hex, "0xdb068094c000000000000000000000000000000000000007830186a0"_hex);
+ trie.insert("0x07"_hex, "0xdb078094c000000000000000000000000000000000000008830186a0"_hex);
+ trie.insert("0x08"_hex, "0xdb088094c000000000000000000000000000000000000009830186a0"_hex);
+ trie.insert("0x09"_hex, "0xdb098094c000000000000000000000000000000000000010830186a0"_hex);
+
+ EXPECT_EQ(
+ trie.hash(), 0xaa45c53e9f7d6a8362f80876029915da00b1441ef39eb9bbb74f98465ff433ad_bytes32);
+}
diff --git a/test/unittests/state_transition_block_test.cpp b/test/unittests/state_transition_block_test.cpp
index 562bd9b70e..16fc817ba6 100644
--- a/test/unittests/state_transition_block_test.cpp
+++ b/test/unittests/state_transition_block_test.cpp
@@ -12,7 +12,7 @@ TEST_F(state_transition, block_apply_withdrawal)
{
static constexpr auto withdrawal_address = 0x8888_address;
- block.withdrawals = {{withdrawal_address, 3}};
+ block.withdrawals = {{0, 0, withdrawal_address, 3}};
tx.to = To;
expect.post[withdrawal_address].balance = intx::uint256{3} * 1'000'000'000;
}
diff --git a/test/unittests/statetest_withdrawals_test.cpp b/test/unittests/statetest_withdrawals_test.cpp
new file mode 100644
index 0000000000..58eb72f37a
--- /dev/null
+++ b/test/unittests/statetest_withdrawals_test.cpp
@@ -0,0 +1,104 @@
+// evmone: Fast Ethereum Virtual Machine implementation
+// Copyright 2023 The evmone Authors.
+// SPDX-License-Identifier: Apache-2.0
+
+#include
+#include
+#include
+
+using namespace evmone;
+using namespace evmone::state;
+using namespace evmone::test;
+
+TEST(statetest_withdrawals, withdrawals_root_hash)
+{
+ // Input taken from https://etherscan.io/block/17826409
+ constexpr std::string_view input =
+ R"([{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe162d9","index":"0xc13ad8","validatorIndex":"0xa2f00"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe1c5c2","index":"0xc13ad9","validatorIndex":"0xa2f01"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe14f28","index":"0xc13ada","validatorIndex":"0xa2f02"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe190f2","index":"0xc13adb","validatorIndex":"0xa2f03"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe1e59c","index":"0xc13adc","validatorIndex":"0xa2f04"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe1bbfe","index":"0xc13add","validatorIndex":"0xa2f05"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe20974","index":"0xc13ade","validatorIndex":"0xa2f06"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe145b7","index":"0xc13adf","validatorIndex":"0xa2f07"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe11e5d","index":"0xc13ae0","validatorIndex":"0xa2f08"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe221e0","index":"0xc13ae1","validatorIndex":"0xa2f09"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe2061a","index":"0xc13ae2","validatorIndex":"0xa2f0a"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe23d22","index":"0xc13ae3","validatorIndex":"0xa2f0b"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe1ab3a","index":"0xc13ae4","validatorIndex":"0xa2f0c"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe19535","index":"0xc13ae5","validatorIndex":"0xa2f0d"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0x317c537","index":"0xc13ae6","validatorIndex":"0xa2f0e"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe1965f","index":"0xc13ae7","validatorIndex":"0xa2f0f"}])";
+
+ const auto j = json::json::parse(input);
+
+ std::vector withdrawals;
+ for (const auto& withdrawal : j)
+ withdrawals.push_back(from_json(withdrawal));
+
+ EXPECT_EQ(mpt_hash(withdrawals),
+ 0x38cd9ae992a22b94a1582e7d0691dbef56a90cdb36bf7b11d98373f80b102c8f_bytes32);
+}
+
+TEST(statetest_withdrawals, withdrawals_warmup_test_case)
+{
+ // Input taken from
+ // https://github.com/ethereum/tests/blob/develop/BlockchainTests/InvalidBlocks/bc4895-withdrawals/warmup.json
+ constexpr std::string_view input =
+ R"([
+{
+ "index" : "0x0",
+ "validatorIndex" : "0x0",
+ "amount" : "0x186a0",
+ "address" : "0xc000000000000000000000000000000000000001"
+},
+{
+ "index" : "0x1",
+ "validatorIndex" : "0x0",
+ "amount" : "0x186a0",
+ "address" : "0xc000000000000000000000000000000000000002"
+},
+{
+ "index" : "0x2",
+ "validatorIndex" : "0x0",
+ "amount" : "0x186a0",
+ "address" : "0xc000000000000000000000000000000000000003"
+},
+{
+ "index" : "0x3",
+ "validatorIndex" : "0x0",
+ "amount" : "0x186a0",
+ "address" : "0xc000000000000000000000000000000000000004"
+},
+{
+ "index" : "0x4",
+ "validatorIndex" : "0x0",
+ "amount" : "0x186a0",
+ "address" : "0xc000000000000000000000000000000000000005"
+},
+{
+ "index" : "0x5",
+ "validatorIndex" : "0x0",
+ "amount" : "0x186a0",
+ "address" : "0xc000000000000000000000000000000000000006"
+},
+{
+ "index" : "0x6",
+ "validatorIndex" : "0x0",
+ "amount" : "0x186a0",
+ "address" : "0xc000000000000000000000000000000000000007"
+},
+{
+ "index" : "0x7",
+ "validatorIndex" : "0x0",
+ "amount" : "0x186a0",
+ "address" : "0xc000000000000000000000000000000000000008"
+},
+{
+ "index" : "0x8",
+ "validatorIndex" : "0x0",
+ "amount" : "0x186a0",
+ "address" : "0xc000000000000000000000000000000000000009"
+},
+{
+ "index" : "0x9",
+ "validatorIndex" : "0x0",
+ "amount" : "0x186a0",
+ "address" : "0xc000000000000000000000000000000000000010"
+}
+])";
+ const auto j = json::json::parse(input);
+
+ std::vector withdrawals;
+ for (const auto& withdrawal : j)
+ withdrawals.push_back(from_json(withdrawal));
+
+ EXPECT_EQ(mpt_hash(withdrawals),
+ 0xaa45c53e9f7d6a8362f80876029915da00b1441ef39eb9bbb74f98465ff433ad_bytes32);
+}