Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RLP encoding implementation to evmone::state library #463

Merged
merged 3 commits into from
Jun 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 12 additions & 0 deletions test/state/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
)
30 changes: 30 additions & 0 deletions test/state/hash_utils.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2022 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <ethash/keccak.hpp>
#include <evmc/evmc.hpp>
#include <evmc/hex.hpp>
#include <cstring>

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
115 changes: 115 additions & 0 deletions test/state/rlp.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2021 The evmone Authors.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started this last year :(

// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <intx/intx.hpp>
#include <cassert>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

namespace evmone::rlp
{
using bytes = std::basic_string<uint8_t>;
using bytes_view = std::basic_string_view<uint8_t>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weren't these defined somewhere? Or only in Fizzy?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already defined in multiple places (2x in EVMC). But as long as the definition is the same this is fine. Notice this RLP implementation does not include any evmone/evmc headers. Also intx include can be easily dropped.


namespace internal
{
template <uint8_t ShortBase, uint8_t LongBase>
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<uint8_t>(ShortBase + l)};
else if (const auto l0 = static_cast<uint8_t>(l); l <= 0xff)
return {LongBase + 1, l0};
else if (const auto l1 = static_cast<uint8_t>(l >> 8); l <= 0xffff)
return {LongBase + 2, l1, l0};
else
return {LongBase + 3, static_cast<uint8_t>(l >> 16), l1, l0};
}

inline bytes wrap_list(const bytes& content)
{
return internal::encode_length<192, 247>(content.size()) + content;
}

template <typename InputIterator>
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 <typename T>
inline decltype(rlp_encode(std::declval<T>())) encode(const T& v)
{
return rlp_encode(v);
gumb0 marked this conversation as resolved.
Show resolved Hide resolved
}

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<short_base, 183>(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 <typename T>
inline bytes encode(const std::vector<T>& v)
{
return internal::encode_container(v.begin(), v.end());
}

template <typename T, size_t N>
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 <typename... Types>
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 <typename InputIterator>
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
3 changes: 2 additions & 1 deletion test/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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/)
Expand Down
125 changes: 125 additions & 0 deletions test/unittests/state_rlp_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2022 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0

#include <gtest/gtest.h>
#include <test/state/hash_utils.hpp>
#include <test/state/rlp.hpp>
#include <test/utils/utils.hpp>

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<uint8_t[]>(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<uint256> 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<CustomStruct> v{{1, {0x02, 0x03}}, {4, {0x05, 0x06}}};
EXPECT_EQ(rlp::encode(v), "ca c401820203 c404820506"_hex);
}