diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index af8eb1dba1..dd89a26e44 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,6 +14,7 @@ add_subdirectory(utils) add_subdirectory(bench) add_subdirectory(integration) add_subdirectory(internal_benchmarks) +add_subdirectory(state) add_subdirectory(unittests) set(targets evmone-bench evmone-bench-internal evmone-unittests testutils) diff --git a/test/state/CMakeLists.txt b/test/state/CMakeLists.txt new file mode 100644 index 0000000000..9d2709a26e --- /dev/null +++ b/test/state/CMakeLists.txt @@ -0,0 +1,12 @@ +# evmone: Fast Ethereum Virtual Machine implementation +# Copyright 2022 The evmone Authors. +# SPDX-License-Identifier: Apache-2.0 + +add_library(evmone-state INTERFACE) +add_library(evmone::state ALIAS evmone-state) +target_link_libraries(evmone-state INTERFACE ethash::keccak) +target_sources( + evmone-state INTERFACE + hash_utils.hpp + rlp.hpp +) diff --git a/test/state/hash_utils.hpp b/test/state/hash_utils.hpp new file mode 100644 index 0000000000..ec5bcda6d7 --- /dev/null +++ b/test/state/hash_utils.hpp @@ -0,0 +1,30 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2022 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include +#include + +namespace evmone +{ +using evmc::bytes; +using evmc::bytes_view; + +/// Default type for 256-bit hash. +/// +/// Better than ethash::hash256 because has some additional handy constructors. +using hash256 = evmc::bytes32; + +/// Computes Keccak hash out of input bytes (wrapper of ethash::keccak256). +inline hash256 keccak256(bytes_view data) noexcept +{ + const auto eh = ethash::keccak256(data.data(), data.size()); + hash256 h; + std::memcpy(h.bytes, eh.bytes, sizeof(h)); // TODO: Use std::bit_cast. + return h; +} +} // namespace evmone diff --git a/test/state/rlp.hpp b/test/state/rlp.hpp new file mode 100644 index 0000000000..574ae73ce9 --- /dev/null +++ b/test/state/rlp.hpp @@ -0,0 +1,115 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2021 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace evmone::rlp +{ +using bytes = std::basic_string; +using bytes_view = std::basic_string_view; + +namespace internal +{ +template +inline bytes encode_length(size_t l) +{ + static constexpr auto short_cutoff = 55; + static_assert(ShortBase + short_cutoff <= 0xff); + assert(l <= 0xffffff); + + if (l <= short_cutoff) + return {static_cast(ShortBase + l)}; + else if (const auto l0 = static_cast(l); l <= 0xff) + return {LongBase + 1, l0}; + else if (const auto l1 = static_cast(l >> 8); l <= 0xffff) + return {LongBase + 2, l1, l0}; + else + return {LongBase + 3, static_cast(l >> 16), l1, l0}; +} + +inline bytes wrap_list(const bytes& content) +{ + return internal::encode_length<192, 247>(content.size()) + content; +} + +template +inline bytes encode_container(InputIterator begin, InputIterator end); +} // namespace internal + +inline bytes_view trim(bytes_view b) noexcept +{ + b.remove_prefix(std::min(b.find_first_not_of(uint8_t{0x00}), b.size())); + return b; +} + +template +inline decltype(rlp_encode(std::declval())) encode(const T& v) +{ + return rlp_encode(v); +} + +inline bytes encode(bytes_view data) +{ + static constexpr uint8_t short_base = 128; + if (data.size() == 1 && data[0] < short_base) + return {data[0]}; + + return internal::encode_length(data.size()) += data; // Op + not available. +} + +inline bytes encode(uint64_t x) +{ + uint8_t b[sizeof(x)]; + intx::be::store(b, x); + return encode(trim({b, sizeof(b)})); +} + +inline bytes encode(const intx::uint256& x) +{ + uint8_t b[sizeof(x)]; + intx::be::store(b, x); + return encode(trim({b, sizeof(b)})); +} + +template +inline bytes encode(const std::vector& v) +{ + return internal::encode_container(v.begin(), v.end()); +} + +template +inline bytes encode(const T (&v)[N]) +{ + return internal::encode_container(std::begin(v), std::end(v)); +} + +/// Encodes the fixed-size collection of heterogeneous values as RLP list. +template +inline bytes encode_tuple(const Types&... elements) +{ + return internal::wrap_list((encode(elements) + ...)); +} + +/// Encodes the container as RLP list. +/// +/// @tparam InputIterator Type of the input iterator. +/// @param begin Begin iterator. +/// @param end End iterator. +/// @return Bytes of the RLP list. +template +inline bytes internal::encode_container(InputIterator begin, InputIterator end) +{ + bytes content; + for (auto it = begin; it != end; ++it) + content += encode(*it); + return wrap_list(content); +} +} // namespace evmone::rlp diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index 90858873a4..f991529328 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -27,10 +27,11 @@ add_executable(evmone-unittests evmone_test.cpp execution_state_test.cpp instructions_test.cpp + state_rlp_test.cpp tracing_test.cpp utils_test.cpp ) -target_link_libraries(evmone-unittests PRIVATE evmone testutils evmc::instructions GTest::gtest GTest::gtest_main) +target_link_libraries(evmone-unittests PRIVATE evmone evmone::state testutils evmc::instructions GTest::gtest GTest::gtest_main) target_include_directories(evmone-unittests PRIVATE ${evmone_private_include_dir}) gtest_discover_tests(evmone-unittests TEST_PREFIX ${PROJECT_NAME}/unittests/) diff --git a/test/unittests/state_rlp_test.cpp b/test/unittests/state_rlp_test.cpp new file mode 100644 index 0000000000..4da782c9a7 --- /dev/null +++ b/test/unittests/state_rlp_test.cpp @@ -0,0 +1,125 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2022 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include + +using namespace evmone; +using namespace evmc::literals; +using namespace intx; + +static constexpr auto emptyBytesHash = + 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470_bytes32; + +static constexpr auto emptyMPTHash = + 0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421_bytes32; + +TEST(state_rlp, empty_bytes_hash) +{ + EXPECT_EQ(keccak256({}), emptyBytesHash); +} + +TEST(state_rlp, empty_mpt_hash) +{ + const auto rlp_null = rlp::encode(0); + EXPECT_EQ(rlp_null, bytes{0x80}); + EXPECT_EQ(keccak256(rlp_null), emptyMPTHash); +} + +TEST(state_rlp, encode_string_short) +{ + EXPECT_EQ(rlp::encode(0x01), "01"_hex); + EXPECT_EQ(rlp::encode(0x31), "31"_hex); + EXPECT_EQ(rlp::encode(0x7f), "7f"_hex); +} + +TEST(state_rlp, encode_string_long) +{ + const auto buffer = std::make_unique(0xffffff); + + const auto r1 = rlp::encode({buffer.get(), 0xaabb}); + EXPECT_EQ(r1.size(), 0xaabb + 3); + EXPECT_EQ(hex({r1.data(), 10}), "b9aabb00000000000000"); + + const auto r2 = rlp::encode({buffer.get(), 0xffff}); + EXPECT_EQ(r2.size(), 0xffff + 3); + EXPECT_EQ(hex({r2.data(), 10}), "b9ffff00000000000000"); + + const auto r3 = rlp::encode({buffer.get(), 0xaabbcc}); + EXPECT_EQ(r3.size(), 0xaabbcc + 4); + EXPECT_EQ(hex({r3.data(), 10}), "baaabbcc000000000000"); + + const auto r4 = rlp::encode({buffer.get(), 0xffffff}); + EXPECT_EQ(r4.size(), 0xffffff + 4); + EXPECT_EQ(hex({r4.data(), 10}), "baffffff000000000000"); +} + +TEST(state_rlp, encode_c_array) +{ + uint64_t a[]{1, 2, 3}; + EXPECT_EQ(hex(rlp::encode(a)), "c3010203"); +} + +TEST(state_rlp, encode_vector) +{ + const auto x = 0xe1e2e3e4e5e6e7d0d1d2d3d4d5d6d7c0c1c2c3c4c5c6c7b0b1b2b3b4b5b6b7_u256; + EXPECT_EQ( + rlp::encode(x), "9fe1e2e3e4e5e6e7d0d1d2d3d4d5d6d7c0c1c2c3c4c5c6c7b0b1b2b3b4b5b6b7"_hex); + std::vector v(0xffffff / 32, x); + const auto r = rlp::encode(v); + EXPECT_EQ(r.size(), v.size() * 32 + 4); +} + +TEST(state_rlp, encode_account_with_balance) +{ + const auto expected = + "f8 44" + "80" + "01" + "a0 56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + "a0 c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"_hex; + + const auto r = rlp::encode_tuple(uint64_t{0}, 1_u256, emptyMPTHash, emptyBytesHash); + EXPECT_EQ(r, expected); +} + +TEST(state_rlp, encode_storage_value) +{ + const auto value = 0x00000000000000000000000000000000000000000000000000000000000001ff_bytes32; + const auto xvalue = rlp::encode(rlp::trim(value)); + EXPECT_EQ(xvalue, "8201ff"_hex); +} + +TEST(state_rlp, encode_mpt_node) +{ + const auto path = "2041"_hex; + const auto value = "765f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f31"_hex; + const auto node = rlp::encode_tuple(path, value); + EXPECT_EQ(node, "e18220419d765f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f31"_hex); +} + +struct CustomStruct +{ + uint64_t a; + bytes b; +}; + +inline bytes rlp_encode(const CustomStruct& t) +{ + return rlp::encode_tuple(t.a, t.b); +} + +TEST(state_rlp, encode_custom_struct) +{ + const CustomStruct t{1, {0x02, 0x03}}; + EXPECT_EQ(rlp::encode(t), "c4 01 820203"_hex); +} + +TEST(state_rlp, encode_custom_struct_list) +{ + std::vector v{{1, {0x02, 0x03}}, {4, {0x05, 0x06}}}; + EXPECT_EQ(rlp::encode(v), "ca c401820203 c404820506"_hex); +}