Skip to content

Commit

Permalink
state: Add RLP encoding implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
chfast committed Jun 15, 2022
1 parent 59f5533 commit 9048bea
Show file tree
Hide file tree
Showing 4 changed files with 245 additions and 1 deletion.
1 change: 1 addition & 0 deletions test/state/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ target_link_libraries(evmone-state INTERFACE ethash::keccak)
target_sources(
evmone-state INTERFACE
hash_utils.hpp
rlp.hpp
)
117 changes: 117 additions & 0 deletions test/state/rlp.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2021 The evmone Authors.
// 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>;

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);
}

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<short_base, 0xb7>(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 <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);
}

0 comments on commit 9048bea

Please sign in to comment.