Skip to content

eth/abi_decoder.cpp

Namespaces

Name
eth
eth::abi

Functions

Name
codec::Hash256 keccak256(const uint8_t * data, size_t len)
Compute Keccak-256 of an arbitrary byte sequence.
codec::Hash256 keccak256(const std::string & text)
Compute Keccak-256 of a string (e.g. an event signature).
codec::Hash256 event_signature_hash(const std::string & signature)
Compute the event topic[0] hash from a human-readable signature.
rlp::Result< AbiValue > decode_abi_word(const codec::Hash256 & word, AbiParamKind kind)
Decode a single 32-byte ABI word from a topic or head slot.
rlp::Result< AbiValue > decode_indexed_param(const codec::Hash256 & topic, const AbiParam & param)
Decode an indexed event parameter from a topic hash.
rlp::Result< std::vector< AbiValue > > decode_log_data(const codec::ByteBuffer & data, const std::vector< AbiParam > & params)
Decode the ABI-encoded data field of a log entry.
rlp::Result< std::vector< AbiValue > > decode_log(const codec::LogEntry & log, const std::string & signature, const std::vector< AbiParam > & params)
Fully decode a log entry given its event descriptor.

Functions Documentation

function keccak256

codec::Hash256 keccak256(
    const uint8_t * data,
    size_t len
)

Compute Keccak-256 of an arbitrary byte sequence.

Parameters:

  • data Input bytes.

Return: 32-byte hash.

function keccak256

codec::Hash256 keccak256(
    const std::string & text
)

Compute Keccak-256 of a string (e.g. an event signature).

function event_signature_hash

codec::Hash256 event_signature_hash(
    const std::string & signature
)

Compute the event topic[0] hash from a human-readable signature.

Parameters:

  • signature e.g. "Transfer(address,address,uint256)"

Return: 32-byte Keccak-256 of the canonical signature string.

function decode_abi_word

rlp::Result< AbiValue > decode_abi_word(
    const codec::Hash256 & word,
    AbiParamKind kind
)

Decode a single 32-byte ABI word from a topic or head slot.

Parameters:

  • word Exactly 32 bytes (ABI head slot or topic).
  • kind Expected parameter type.

Return: Decoded value or error.

  • kAddress → reads rightmost 20 bytes
  • kUint → reads as big-endian uint256
  • kInt → reads as big-endian uint256 (raw bits, no sign extension)
  • kBytes32 → returns the 32 bytes directly
  • kBool → reads last byte, non-zero = true

function decode_indexed_param

rlp::Result< AbiValue > decode_indexed_param(
    const codec::Hash256 & topic,
    const AbiParam & param
)

Decode an indexed event parameter from a topic hash.

Parameters:

  • topic The 32-byte topic value.
  • param Parameter descriptor (kind, name).

For value types (address, uint, bool, bytes32) the topic IS the 32-byte ABI-encoded value. Dynamic types (bytes, string) are hashed and cannot be recovered — an empty ByteBuffer / string is returned.

function decode_log_data

rlp::Result< std::vector< AbiValue > > decode_log_data(
    const codec::ByteBuffer & data,
    const std::vector< AbiParam > & params
)

Decode the ABI-encoded data field of a log entry.

Parameters:

  • data Raw bytes from LogEntry::data.
  • params Ordered list of non-indexed parameters to decode.

Return: Vector of decoded values in the same order as params, or error.

Decodes the non-indexed parameters from the log's data field following the standard ABI head/tail encoding (EIP-838 / Solidity ABI spec).

function decode_log

rlp::Result< std::vector< AbiValue > > decode_log(
    const codec::LogEntry & log,
    const std::string & signature,
    const std::vector< AbiParam > & params
)

Fully decode a log entry given its event descriptor.

Parameters:

  • log The raw log entry.
  • signature Human-readable event signature, e.g. "Transfer(address,address,uint256)".
  • params Full ordered parameter list (indexed + non-indexed). topic[0] is implicitly the signature hash and is not listed here.

Return: Decoded values in declaration order (indexed first, then data), or error.

Combines topic decoding (indexed params) and data decoding (non-indexed).

Source code

// Copyright 2025 GeniusVentures
// SPDX-License-Identifier: Apache-2.0

#include <eth/abi_decoder.hpp>
#include <eth/eth_constants.hpp>
#include <rlp/errors.hpp>
#include <algorithm>

#include <nil/crypto3/hash/algorithm/hash.hpp>
#include <nil/crypto3/hash/keccak.hpp>

namespace eth::abi {

namespace {

// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------

intx::uint256 read_uint256_be(const uint8_t* data) noexcept
{
    auto read64be = [](const uint8_t* p) -> uint64_t
    {
        return (static_cast<uint64_t>(p[0]) << 56)
             | (static_cast<uint64_t>(p[1]) << 48)
             | (static_cast<uint64_t>(p[2]) << 40)
             | (static_cast<uint64_t>(p[3]) << 32)
             | (static_cast<uint64_t>(p[4]) << 24)
             | (static_cast<uint64_t>(p[5]) << 16)
             | (static_cast<uint64_t>(p[6]) <<  8)
             |  static_cast<uint64_t>(p[7]);
    };

    // Variadic ctor fills words_[] in order: words_[0], words_[1], words_[2], words_[3]
    // words_[0] = LS 64 bits = data[24..31]
    // words_[3] = MS 64 bits = data[0..7]
    return intx::uint256{
        read64be(data + 24),   // words_[0] — least significant
        read64be(data + 16),   // words_[1]
        read64be(data +  8),   // words_[2]
        read64be(data +  0)    // words_[3] — most significant
    };
}

bool is_dynamic(AbiParamKind kind) noexcept
{
    return kind == AbiParamKind::kBytes || kind == AbiParamKind::kString;
}

} // namespace

// ---------------------------------------------------------------------------
// keccak256
// ---------------------------------------------------------------------------

codec::Hash256 keccak256(const uint8_t* data, size_t len) noexcept
{
    auto hash = nil::crypto3::hash<nil::crypto3::hashes::keccak_1600<256>>(data, data + len);
    std::array<uint8_t, kKeccak256Size> hash_array = hash;
    codec::Hash256 out{};
    std::copy(hash_array.begin(), hash_array.end(), out.begin());
    return out;
}

codec::Hash256 keccak256(const std::string& text) noexcept
{
    return keccak256(reinterpret_cast<const uint8_t*>(text.data()), text.size());
}

codec::Hash256 event_signature_hash(const std::string& signature) noexcept
{
    return keccak256(signature);
}

// ---------------------------------------------------------------------------
// decode_abi_word
// ---------------------------------------------------------------------------

rlp::Result<AbiValue> decode_abi_word(
    const codec::Hash256& word,
    AbiParamKind          kind) noexcept
{
    switch (kind)
    {
        case AbiParamKind::kAddress:
        {
            // Address occupies the rightmost kAbiAddressSize bytes of the 32-byte word.
            codec::Address addr{};
            std::copy(word.begin() + kAbiAddressPadding, word.end(), addr.begin());
            return AbiValue{addr};
        }

        case AbiParamKind::kUint:
        case AbiParamKind::kInt:
        {
            return AbiValue{read_uint256_be(word.data())};
        }

        case AbiParamKind::kBytes32:
        {
            return AbiValue{word};
        }

        case AbiParamKind::kBool:
        {
            // Any non-zero byte in the word means true; canonically it's word[kAbiBoolByteIndex].
            bool val = (word[kAbiBoolByteIndex] != 0);
            return AbiValue{val};
        }

        case AbiParamKind::kBytes:
        case AbiParamKind::kString:
        {
            // Dynamic types are hashed when indexed — value cannot be recovered.
            // Return empty placeholder.
            if (kind == AbiParamKind::kBytes)
            {
                return AbiValue{codec::ByteBuffer{}};
            }
            return AbiValue{std::string{}};
        }
    }

    return rlp::DecodingError::kUnexpectedString;
}

// ---------------------------------------------------------------------------
// decode_indexed_param
// ---------------------------------------------------------------------------

rlp::Result<AbiValue> decode_indexed_param(
    const codec::Hash256& topic,
    const AbiParam&       param) noexcept
{
    return decode_abi_word(topic, param.kind);
}

// ---------------------------------------------------------------------------
// decode_log_data
// ---------------------------------------------------------------------------

rlp::Result<std::vector<AbiValue>> decode_log_data(
    const codec::ByteBuffer&     data,
    const std::vector<AbiParam>& params) noexcept
{
    if (params.empty())
    {
        return std::vector<AbiValue>{};
    }

    // Each head slot is kAbiWordSize bytes.  Dynamic types store an offset in the head.
    const size_t head_size = params.size() * kAbiWordSize;

    if (data.size() < head_size)
    {
        return rlp::DecodingError::kInputTooShort;
    }

    std::vector<AbiValue> results;
    results.reserve(params.size());

    for (size_t i = 0; i < params.size(); ++i)
    {
        const AbiParamKind kind = params[i].kind;
        const size_t       head_offset = i * kAbiWordSize;

        if (!is_dynamic(kind))
        {
            codec::Hash256 word{};
            std::copy(
                data.data() + head_offset,
                data.data() + head_offset + kAbiWordSize,
                word.begin());

            auto val = decode_abi_word(word, kind);
            if (!val) { return val.error(); }
            results.push_back(std::move(val.value()));
        }
        else
        {
            // Head slot holds the byte offset into the data buffer where the
            // actual value starts.
            codec::Hash256 offset_word{};
            std::copy(
                data.data() + head_offset,
                data.data() + head_offset + kAbiWordSize,
                offset_word.begin());

            const intx::uint256 raw_offset = read_uint256_be(offset_word.data());

            // Offset must fit in size_t and point inside the buffer.
            if (raw_offset > std::numeric_limits<size_t>::max())
            {
                return rlp::DecodingError::kOverflow;
            }

            const size_t tail_offset = static_cast<size_t>(raw_offset);

            // Read kAbiWordSize-byte length prefix at tail_offset.
            if (data.size() < tail_offset + kAbiWordSize)
            {
                return rlp::DecodingError::kInputTooShort;
            }

            codec::Hash256 len_word{};
            std::copy(
                data.data() + tail_offset,
                data.data() + tail_offset + kAbiWordSize,
                len_word.begin());

            const intx::uint256 raw_len = read_uint256_be(len_word.data());

            if (raw_len > std::numeric_limits<size_t>::max())
            {
                return rlp::DecodingError::kOverflow;
            }

            const size_t content_len    = static_cast<size_t>(raw_len);
            const size_t content_offset = tail_offset + kAbiWordSize;

            if (data.size() < content_offset + content_len)
            {
                return rlp::DecodingError::kInputTooShort;
            }

            if (kind == AbiParamKind::kBytes)
            {
                codec::ByteBuffer buf(
                    data.data() + content_offset,
                    data.data() + content_offset + content_len);
                results.push_back(AbiValue{std::move(buf)});
            }
            else // kString
            {
                std::string str(
                    reinterpret_cast<const char*>(data.data() + content_offset),
                    content_len);
                results.push_back(AbiValue{std::move(str)});
            }
        }
    }

    return results;
}

// ---------------------------------------------------------------------------
// decode_log
// ---------------------------------------------------------------------------

rlp::Result<std::vector<AbiValue>> decode_log(
    const codec::LogEntry&       log,
    const std::string&           signature,
    const std::vector<AbiParam>& params) noexcept
{
    // Verify topic[0] matches the signature hash.
    if (log.topics.empty())
    {
        return rlp::DecodingError::kInputTooShort;
    }

    const codec::Hash256 expected_sig = event_signature_hash(signature);
    if (log.topics[0] != expected_sig)
    {
        return rlp::DecodingError::kUnexpectedString;
    }

    std::vector<AbiValue> results;
    results.reserve(params.size());

    size_t topic_index = 1; // topics[0] is the signature hash
    std::vector<AbiParam> data_params;

    for (const auto& param : params)
    {
        if (param.indexed)
        {
            if (topic_index >= log.topics.size())
            {
                return rlp::DecodingError::kInputTooShort;
            }

            auto val = decode_indexed_param(log.topics[topic_index], param);
            if (!val) { return val.error(); }
            results.push_back(std::move(val.value()));
            ++topic_index;
        }
        else
        {
            data_params.push_back(param);
        }
    }

    // Decode non-indexed params from data field.
    auto data_vals = decode_log_data(log.data, data_params);
    if (!data_vals) { return data_vals.error(); }

    for (auto& v : data_vals.value())
    {
        results.push_back(std::move(v));
    }

    return results;
}

} // namespace eth::abi

Updated on 2026-04-13 at 23:22:46 -0700