Skip to content
Open
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
140 changes: 84 additions & 56 deletions category/execution/ethereum/rlp/decode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@

#include <boost/outcome/try.hpp>

#include <utility>

MONAD_RLP_NAMESPACE_BEGIN

template <unsigned_integral T>
Expand Down Expand Up @@ -56,90 +58,116 @@ constexpr Result<size_t> decode_length(byte_string_view const enc)
return decode_raw_num<size_t>(enc);
}

constexpr Result<byte_string_view> parse_string_metadata(byte_string_view &enc)
enum class RlpType
{
size_t i = 0;
size_t end = 0;
String,
List
};

if (MONAD_UNLIKELY(enc.empty())) {
return DecodeError::InputTooShort;
}
namespace detail
{
[[gnu::always_inline]] inline constexpr Result<
std::pair<RlpType, byte_string_view>>
parse_metadata_raw(byte_string_view &enc)
{
size_t i = 0;
size_t length;
RlpType type;

if (MONAD_UNLIKELY(enc[0] >= 0xc0)) {
return DecodeError::TypeUnexpected;
}
MONAD_DEBUG_ASSERT(!enc.empty())

if (enc[0] < 0x80) // [0x00, 0x7f]
{
end = i + 1;
}
else if (enc[0] < 0xb8) // [0x80, 0xb7]
{
++i;
uint8_t const length = enc[0] - 0x80;
end = i + length;
}
else // [0xb8, 0xbf]
{
++i;
uint8_t const length_of_length = enc[0] - 0xb7;
if (enc[0] < 0x80) // [0x00, 0x7f]
Comment thread
goodlyrottenapple marked this conversation as resolved.
{
type = RlpType::String;
length = 1;
}
else if (enc[0] < 0xb8) // [0x80, 0xb7]
{
type = RlpType::String;
length = enc[0] - 0x80;
++i;
}
else if (enc[0] < 0xc0) // [0xb8, 0xbf]
{
type = RlpType::String;
uint8_t const length_of_length = enc[0] - 0xb7;
++i;
if (MONAD_UNLIKELY(i + length_of_length >= enc.size())) {
return DecodeError::InputTooShort;
}
BOOST_OUTCOME_TRY(
length, decode_length(enc.substr(i, length_of_length)));
i += length_of_length;
}
else if (enc[0] < 0xf8) // [0xc0, 0xf7]
{
type = RlpType::List;
length = enc[0] - 0xc0;
++i;
}
else // [0xf8, 0xff]
{
type = RlpType::List;
size_t const length_of_length = enc[0] - 0xf7;
++i;
if (MONAD_UNLIKELY(i + length_of_length >= enc.size())) {
return DecodeError::InputTooShort;
}
BOOST_OUTCOME_TRY(
length, decode_length(enc.substr(i, length_of_length)));
i += length_of_length;
}

auto const end = i + length;

if (MONAD_UNLIKELY(i + length_of_length >= enc.size())) {
if (MONAD_UNLIKELY(end > enc.size() || end < i)) {
return DecodeError::InputTooShort;
}

BOOST_OUTCOME_TRY(
auto const length, decode_length(enc.substr(i, length_of_length)));
i += length_of_length;
end = i + length;
auto const payload = enc.substr(i, length);
enc = enc.substr(end);
return std::pair{type, payload};
}
}

if (MONAD_UNLIKELY(end > enc.size() || end < i)) {
constexpr Result<std::pair<RlpType, byte_string_view>>
parse_metadata(byte_string_view &enc)
Comment on lines +133 to +134
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

parse_metadata() returns a std::pair<RlpType, byte_string_view>, which forces callers to use .first/.second and makes it easy to accidentally swap or misread the fields. Consider returning a small named struct (e.g., {RlpType type; byte_string_view payload;}) or a dedicated alias/type to make the API self-documenting.

Copilot uses AI. Check for mistakes.
{
if (MONAD_UNLIKELY(enc.empty())) {
return DecodeError::InputTooShort;
}

auto const payload = enc.substr(i, end - i);
enc = enc.substr(end);
return payload;
return detail::parse_metadata_raw(enc);
}
Comment thread
goodlyrottenapple marked this conversation as resolved.

constexpr Result<byte_string_view> parse_list_metadata(byte_string_view &enc)
constexpr Result<byte_string_view> parse_string_metadata(byte_string_view &enc)
{
size_t i = 0;
size_t length;
++i;

if (MONAD_UNLIKELY(enc.empty())) {
return DecodeError::InputTooShort;
}

if (MONAD_UNLIKELY(enc[0] < 0xc0)) {
if (MONAD_UNLIKELY(enc[0] >= 0xc0)) {
return DecodeError::TypeUnexpected;
}

if (enc[0] < 0xf8) {
length = enc[0] - 0xc0;
}
else {
size_t const length_of_length = enc[0] - 0xf7;

if (MONAD_UNLIKELY(i + length_of_length >= enc.size())) {
return DecodeError::InputTooShort;
}
BOOST_OUTCOME_TRY(auto const result, detail::parse_metadata_raw(enc));
MONAD_DEBUG_ASSERT(result.first == RlpType::String);
return result.second;
}

BOOST_OUTCOME_TRY(
length, decode_length(enc.substr(i, length_of_length)));
i += length_of_length;
constexpr Result<byte_string_view> parse_list_metadata(byte_string_view &enc)
{
if (MONAD_UNLIKELY(enc.empty())) {
return DecodeError::InputTooShort;
}
auto const end = i + length;

if (MONAD_UNLIKELY(end > enc.size() || end < i)) {
return DecodeError::InputTooShort;
if (MONAD_UNLIKELY(enc[0] < 0xc0)) {
return DecodeError::TypeUnexpected;
}

auto const payload = enc.substr(i, end - i);
enc = enc.substr(end);
return payload;
BOOST_OUTCOME_TRY(auto const result, detail::parse_metadata_raw(enc));
MONAD_DEBUG_ASSERT(result.first == RlpType::List);
return result.second;
}

constexpr Result<byte_string_view> decode_string(byte_string_view &enc)
Expand Down
40 changes: 40 additions & 0 deletions category/execution/ethereum/rlp/test/test_decode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

#include <category/core/byte_string.hpp>
#include <category/execution/ethereum/rlp/decode.hpp>
#include <category/execution/ethereum/rlp/decode_error.hpp>
#include <category/execution/ethereum/rlp/encode2.hpp>

#include <gtest/gtest.h>
Expand Down Expand Up @@ -60,3 +61,42 @@ TEST(Rlp, DecodeAfterEncodeString)
EXPECT_EQ(decoded_string.value(), to_byte_string_view(long_string));
}
}

TEST(Rlp, ParseMetadata)
{
// String payload
{
auto encoding = encode_string2(to_byte_string_view("dog"));
byte_string_view enc{encoding};

auto const result = parse_metadata(enc);
ASSERT_FALSE(result.has_error());
EXPECT_EQ(result.value().first, RlpType::String);
EXPECT_EQ(result.value().second, to_byte_string_view("dog"));
EXPECT_EQ(enc.size(), 0);
}

// List payload
{
auto const inner =
monad::byte_string({0x83, 'c', 'a', 't', 0x83, 'd', 'o', 'g'});
auto encoding = encode_list2(
encode_string2(to_byte_string_view("cat")),
encode_string2(to_byte_string_view("dog")));
byte_string_view enc{encoding};

auto const result = parse_metadata(enc);
ASSERT_FALSE(result.has_error());
EXPECT_EQ(result.value().first, RlpType::List);
EXPECT_EQ(result.value().second, byte_string_view{inner});
EXPECT_EQ(enc.size(), 0);
}

// Empty input
{
byte_string_view enc{};
auto const result = parse_metadata(enc);
ASSERT_TRUE(result.has_error());
EXPECT_EQ(result.error(), DecodeError::InputTooShort);
}
}
Loading