From 0f694c58a4ebdc4766a6f044a904a2c92371fead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Fri, 6 May 2022 22:40:04 +0200 Subject: [PATCH] state: Add RLP encoding implementation --- test/state/CMakeLists.txt | 1 + test/state/rlp.hpp | 117 ++++++++++++++++++++++++++++++ test/unittests/CMakeLists.txt | 3 +- test/unittests/state_rlp_test.cpp | 103 ++++++++++++++++++++++++++ 4 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 test/state/rlp.hpp create mode 100644 test/unittests/state_rlp_test.cpp diff --git a/test/state/CMakeLists.txt b/test/state/CMakeLists.txt index bc9d7c5a9e..9d2709a26e 100644 --- a/test/state/CMakeLists.txt +++ b/test/state/CMakeLists.txt @@ -8,4 +8,5 @@ target_link_libraries(evmone-state INTERFACE ethash::keccak) target_sources( evmone-state INTERFACE hash_utils.hpp + rlp.hpp ) diff --git a/test/state/rlp.hpp b/test/state/rlp.hpp new file mode 100644 index 0000000000..2ca7bbfecc --- /dev/null +++ b/test/state/rlp.hpp @@ -0,0 +1,117 @@ +// 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<0xc0, 0xf7>(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 = 0x80; + if (data.size() == 1 && data[0] < short_base) + return {data[0]}; + + auto r = internal::encode_length(data.size()); + r += data; + return r; +} + +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 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 49ff0527de..e9fa5abfd3 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -26,10 +26,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..d4b4c8762a --- /dev/null +++ b/test/unittests/state_rlp_test.cpp @@ -0,0 +1,103 @@ +// 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(hex(rlp::encode(0x01)), "01"); + EXPECT_EQ(hex(rlp::encode(0x31)), "31"); + EXPECT_EQ(hex(rlp::encode(0x7f)), "7f"); +} + +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( + hex(rlp::encode(x)), "9fe1e2e3e4e5e6e7d0d1d2d3d4d5d6d7c0c1c2c3c4c5c6c7b0b1b2b3b4b5b6b7"); + 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 = from_hex( + "f8 44" + "80" + "01" + "a0 56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + "a0 c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"); + + const auto r = rlp::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, from_hex("8201ff")); +} + +TEST(state_rlp, encode_mpt_node) +{ + const bytes path{0x20, 0x41}; + const bytes value{'v', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', + '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '1'}; + const auto node = rlp::tuple(path, value); + EXPECT_EQ(hex(node), "e18220419d765f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f31"); +}