diff --git a/include/evmc/hex.hpp b/include/evmc/hex.hpp index 907b89d09..064ebf3d2 100644 --- a/include/evmc/hex.hpp +++ b/include/evmc/hex.hpp @@ -113,4 +113,26 @@ inline std::optional from_hex(std::string_view hex) return {}; return bs; } + +/// Decodes hex-encoded string into custom type T with .bytes array of uint8_t. +/// +/// When the input is smaller than the result type, the result is padded with zeros on the left +/// (the result bytes of lowest indices are filled with zeros). +/// TODO: Support optional left alignment. +template +constexpr std::optional from_hex(std::string_view s) noexcept +{ + // Omit the optional 0x prefix. + if (s.size() >= 2 && s[0] == '0' && s[1] == 'x') + s.remove_prefix(2); + + T r{}; // The T must have .bytes array. This may be lifted if std::bit_cast is available. + constexpr auto num_out_bytes = std::size(r.bytes); + const auto num_in_bytes = s.length() / 2; + if (num_in_bytes > num_out_bytes) + return {}; + if (!from_hex(s.begin(), s.end(), &r.bytes[num_out_bytes - num_in_bytes])) + return {}; + return r; +} } // namespace evmc diff --git a/test/unittests/hex_test.cpp b/test/unittests/hex_test.cpp index b7a221b2c..dfc9a5098 100644 --- a/test/unittests/hex_test.cpp +++ b/test/unittests/hex_test.cpp @@ -61,8 +61,16 @@ TEST(hex, from_hex_0x_prefix) EXPECT_EQ(from_hex("0x01020304"), (bytes{0x01, 0x02, 0x03, 0x04})); EXPECT_EQ(from_hex("0x123"), std::nullopt); EXPECT_EQ(from_hex("00x"), std::nullopt); + EXPECT_EQ(from_hex("000s"), std::nullopt); EXPECT_EQ(from_hex("00x0"), std::nullopt); + EXPECT_EQ(from_hex("0x001x"), std::nullopt); EXPECT_EQ(from_hex("0x001y"), std::nullopt); + EXPECT_EQ(from_hex("1x"), std::nullopt); + EXPECT_EQ(from_hex("1x00"), std::nullopt); + EXPECT_EQ(from_hex("0y"), std::nullopt); + EXPECT_EQ(from_hex("0y00"), std::nullopt); + EXPECT_EQ(from_hex("1y"), std::nullopt); + EXPECT_EQ(from_hex("1y00"), std::nullopt); } TEST(hex, validate_hex) @@ -90,3 +98,43 @@ TEST(hex, from_hex_skip_space) EXPECT_EQ(from_hex_skip_space(" 0 x 0 1 0 2 0 3 "), (bytes{0x01, 0x02, 0x03})); EXPECT_EQ(from_hex_skip_space("\f 0\r x 0 1\t 0 2 \v0 3 \n"), (bytes{0x01, 0x02, 0x03})); } + +TEST(hex, from_hex_to_custom_type) +{ + struct X + { + uint8_t bytes[4]; + }; + constexpr auto test = [](std::string_view in) { + return evmc::hex({evmc::from_hex(in).value().bytes, sizeof(X)}); + }; + + static_assert(evmc::from_hex("01").value().bytes[3] == 0x01); // Works in constexpr. + + EXPECT_EQ(test("f1f2f3f4"), "f1f2f3f4"); + EXPECT_EQ(test("01020304"), "01020304"); + EXPECT_EQ(test("010203"), "00010203"); + EXPECT_EQ(test("0102"), "00000102"); + EXPECT_EQ(test("01"), "00000001"); + EXPECT_EQ(test(""), "00000000"); + + EXPECT_EQ(test("0x01020304"), "01020304"); + EXPECT_EQ(test("0x010203"), "00010203"); + EXPECT_EQ(test("0x0102"), "00000102"); + EXPECT_EQ(test("0x01"), "00000001"); + EXPECT_EQ(test("0x"), "00000000"); + + EXPECT_FALSE(evmc::from_hex("0")); + EXPECT_FALSE(evmc::from_hex("1")); + EXPECT_FALSE(evmc::from_hex("0x ")); + EXPECT_FALSE(evmc::from_hex("0xf")); + EXPECT_FALSE(evmc::from_hex("0x 00")); + EXPECT_FALSE(evmc::from_hex("1x")); + EXPECT_FALSE(evmc::from_hex("1x00")); + EXPECT_FALSE(evmc::from_hex("fx")); + EXPECT_FALSE(evmc::from_hex("fx00")); + + // The result type is too small for the input. + EXPECT_FALSE(evmc::from_hex("0000000000")); + EXPECT_FALSE(evmc::from_hex("0x0000000000")); +}