mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-20 13:40:44 +00:00
Compare commits
1 Commits
ripple/was
...
wasmi/bina
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03511bf0ec |
37
include/xrpl/tx/wasm/Codec.h
Normal file
37
include/xrpl/tx/wasm/Codec.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/tx/wasm/CodecEnvelope.h>
|
||||
|
||||
namespace xrpl::wasm {
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Public wasm codec API.
|
||||
//
|
||||
// This header is what host-function code includes. It intentionally contains
|
||||
// NO codec machinery, NO registered-codec list, and NO concrete type
|
||||
// registrations — only the two entry points. Their definitions are provided by
|
||||
// explicit instantiation in src/libxrpl/tx/wasm/codecs/CodecRegistry.cpp, so
|
||||
// including this header does not instantiate the (consteval) dispatch tables.
|
||||
//
|
||||
// A decode<T>/encode<T> for an unregistered T is a link error by design.
|
||||
//
|
||||
// To add a codec, see include/xrpl/tx/wasm/detail/codec_types.macro.
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// Decode a value of the statically-known type T from a self-describing
|
||||
// envelope. The envelope carries its own (type_code, version), so no revision
|
||||
// context is needed here.
|
||||
template <typename T>
|
||||
[[nodiscard]] Expected<T, CodecError>
|
||||
decode(CodecEnvelope const& env);
|
||||
|
||||
// Encode `value` toward a contract that declared codec revision
|
||||
// `declaredRevision`. The wire version emitted is chosen by the revision policy
|
||||
// (wireVersionFor); if the value cannot be represented in that version the
|
||||
// result is NotRepresentable (the graceful-fail path).
|
||||
template <typename T>
|
||||
[[nodiscard]] Expected<CodecEnvelope, CodecError>
|
||||
encode(CodecVersion declaredRevision, T const& value);
|
||||
|
||||
} // namespace xrpl::wasm
|
||||
35
include/xrpl/tx/wasm/CodecEnvelope.h
Normal file
35
include/xrpl/tx/wasm/CodecEnvelope.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl::wasm {
|
||||
|
||||
using CodecTypeCode = std::uint16_t;
|
||||
using CodecVersion = std::uint8_t;
|
||||
using CodecLength = std::uint32_t;
|
||||
|
||||
enum class CodecError : std::uint8_t {
|
||||
Ok = 0,
|
||||
UnknownType = 1, // no type code registered for the type
|
||||
UnsupportedVersion = 2, // The envelope's version has no registered codec
|
||||
TypeMismatch = 3, // When decoding, the codec called does not match the type of the envelope
|
||||
Truncated = 4, // payload size != fixed payload size
|
||||
NotRepresentable = 5, // when encoding, the value doesn't fit within the requested version
|
||||
TypeNotInRevision = 6, // There is no codec for the type in the requested version
|
||||
UnknownCodecVersion = 7, // The codec version is not registered
|
||||
};
|
||||
|
||||
// Defines the header of the binary payload for each parameter based through the host functions
|
||||
struct CodecEnvelopeHeader {
|
||||
CodecTypeCode type_code;
|
||||
CodecVersion version;
|
||||
CodecLength payload_size; // I would prefer a variable sized integer, will need to revisit this part.
|
||||
};
|
||||
|
||||
// Defines the binary payload for each parameter based through the host functions
|
||||
struct CodecEnvelope {
|
||||
CodecEnvelopeHeader header;
|
||||
std::uint8_t const* payload; // Unclear if this can be a pointer into the wasm payload or a vector holding a copy.
|
||||
};
|
||||
|
||||
} // namespace xrpl::wasm
|
||||
145
include/xrpl/tx/wasm/detail/CodecDispatch.h
Normal file
145
include/xrpl/tx/wasm/detail/CodecDispatch.h
Normal file
@@ -0,0 +1,145 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/tx/wasm/CodecEnvelope.h>
|
||||
#include <xrpl/tx/wasm/detail/CodecTypeCode.h>
|
||||
#include <xrpl/tx/wasm/detail/CodecTypeTrait.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl::wasm::detail {
|
||||
|
||||
// Sentinel that lets the registry's X-macro expansion end with a trailing
|
||||
// comma: CodecList<Trait0, Trait1, CodecEnd>. Its `void` CodecType never
|
||||
// matches a real type, so every table builder simply skips it.
|
||||
struct CodecEnd
|
||||
{
|
||||
using CodecType = void;
|
||||
};
|
||||
|
||||
template <typename Trait, typename T>
|
||||
inline constexpr bool traitIsV = std::is_same_v<typename Trait::CodecType, T>;
|
||||
|
||||
template <typename T>
|
||||
using DecodeFn = Expected<T, CodecError> (*)(CodecEnvelope const&);
|
||||
|
||||
template <typename T>
|
||||
using EncodeFn = Expected<CodecEnvelope, CodecError> (*)(T const&);
|
||||
|
||||
// Highest registered version of T within List. Unregistered T ⇒ build error.
|
||||
template <typename T, typename List, std::size_t... I>
|
||||
consteval CodecVersion
|
||||
maxVersionImpl(std::index_sequence<I...>)
|
||||
{
|
||||
auto v = CodecVersion{};
|
||||
auto found = false;
|
||||
([&] {
|
||||
using Trait = std::tuple_element_t<I, List>;
|
||||
if constexpr (traitIsV<Trait, T>)
|
||||
{
|
||||
found = true;
|
||||
v = std::max(v, Trait::kVersion);
|
||||
}
|
||||
}(), ...);
|
||||
if (!found)
|
||||
{
|
||||
xrpl::Throw<std::runtime_error>("wasm codec: no codec registered for this type");
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
template <typename T, typename List>
|
||||
consteval CodecVersion
|
||||
maxVersion()
|
||||
{
|
||||
return maxVersionImpl<T, List>(
|
||||
std::make_index_sequence<std::tuple_size_v<List>>{});
|
||||
}
|
||||
|
||||
template <typename T, typename List, std::size_t... I>
|
||||
consteval auto
|
||||
makeDecoderTableImpl(std::index_sequence<I...>)
|
||||
{
|
||||
constexpr auto n = maxVersion<T, List>() + 1;
|
||||
auto table = std::array<DecodeFn<T>, n>{};
|
||||
([&] {
|
||||
using Trait = std::tuple_element_t<I, List>;
|
||||
if constexpr (traitIsV<Trait, T>)
|
||||
{
|
||||
table[Trait::kVersion] = &Trait::decode;
|
||||
}
|
||||
}(), ...);
|
||||
for (const auto& entry : table)
|
||||
{
|
||||
if (!entry)
|
||||
{
|
||||
xrpl::Throw<std::runtime_error>("wasm codec: decoder versions must be contiguous from 0");
|
||||
}
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
template <typename T, typename List>
|
||||
inline constexpr auto decoderTableV = makeDecoderTableImpl<T, List>(
|
||||
std::make_index_sequence<std::tuple_size_v<List>>{});
|
||||
|
||||
template <typename T, typename List, std::size_t... I>
|
||||
consteval auto
|
||||
makeEncoderTableImpl(std::index_sequence<I...>)
|
||||
{
|
||||
constexpr auto n = maxVersion<T, List>() + 1;
|
||||
auto table = std::array<EncodeFn<T>, n>{};
|
||||
([&] {
|
||||
using Trait = std::tuple_element_t<I, List>;
|
||||
if constexpr (traitIsV<Trait, T>)
|
||||
{
|
||||
table[Trait::kVersion] = &Trait::encode;
|
||||
}
|
||||
}(), ...);
|
||||
for (const auto& entry : table)
|
||||
{
|
||||
if (!entry)
|
||||
{
|
||||
xrpl::Throw<std::runtime_error>("wasm codec: encoder versions must be contiguous from 0");
|
||||
}
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
template <typename T, typename List>
|
||||
inline constexpr auto encoderTableV = makeEncoderTableImpl<T, List>(
|
||||
std::make_index_sequence<std::tuple_size_v<List>>{});
|
||||
|
||||
template <typename T, typename List>
|
||||
Expected<T, CodecError>
|
||||
decodeImpl(CodecEnvelope const& env)
|
||||
{
|
||||
if (env.header.type_code != codecTypeCodeV<T>)
|
||||
{
|
||||
return Unexpected{CodecError::TypeMismatch};
|
||||
}
|
||||
auto const& table = decoderTableV<T, List>;
|
||||
if (env.header.version >= table.size())
|
||||
{
|
||||
return Unexpected{CodecError::UnsupportedVersion};
|
||||
}
|
||||
return table[env.header.version](env);
|
||||
}
|
||||
|
||||
template <typename T, typename List>
|
||||
Expected<CodecEnvelope, CodecError>
|
||||
encodeAt(CodecVersion wireVersion, T const& value)
|
||||
{
|
||||
auto const& table = encoderTableV<T, List>;
|
||||
if (wireVersion >= table.size())
|
||||
{
|
||||
return Unexpected{CodecError::UnsupportedVersion};
|
||||
}
|
||||
return table[wireVersion](value);
|
||||
}
|
||||
|
||||
} // namespace xrpl::wasm::detail
|
||||
12
include/xrpl/tx/wasm/detail/CodecList.h
Normal file
12
include/xrpl/tx/wasm/detail/CodecList.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <tuple>
|
||||
|
||||
namespace xrpl::wasm::detail {
|
||||
|
||||
template <typename... Traits>
|
||||
struct CodecList {
|
||||
using TupleType = std::tuple<Traits...>;
|
||||
};
|
||||
|
||||
} // namespace xrpl::wasm::detail
|
||||
18
include/xrpl/tx/wasm/detail/CodecRevisions.h
Normal file
18
include/xrpl/tx/wasm/detail/CodecRevisions.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/tx/wasm/CodecEnvelope.h>
|
||||
|
||||
namespace xrpl::wasm::detail {
|
||||
|
||||
// Maps a contract's declared codec revision to the per-type wire version to
|
||||
// emit for `code`. This is the revision/amendment POLICY layer, kept separate
|
||||
// from both the generic dispatch machinery and the concrete value codecs.
|
||||
//
|
||||
// Defined in src/libxrpl/tx/wasm/codecs/CodecRevisions.cpp alongside the
|
||||
// concrete CodecTrait<N> revision maps. (Amendment gating via Rules is a
|
||||
// deliberate TODO there — see the .cpp.)
|
||||
Expected<CodecVersion, CodecError>
|
||||
wireVersionFor(CodecVersion declaredRevision, CodecTypeCode code);
|
||||
|
||||
} // namespace xrpl::wasm::detail
|
||||
18
include/xrpl/tx/wasm/detail/CodecTrait.h
Normal file
18
include/xrpl/tx/wasm/detail/CodecTrait.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/wasm/CodecEnvelope.h>
|
||||
|
||||
namespace xrpl::wasm::detail {
|
||||
|
||||
template <CodecVersion Version>
|
||||
struct CodecTrait;
|
||||
|
||||
// template <>
|
||||
// struct CodecTrait<0> {
|
||||
// static constexpr auto kVersion = CodecVersion{0};
|
||||
// static constexpr auto versions = std::to_array<std::pair<CodecTypeCode, CodecVersion>>({
|
||||
// {codecTypeCodeV<STNumber>, 0},
|
||||
// });
|
||||
// };
|
||||
|
||||
} // namespace xrpl::wasm::detail
|
||||
21
include/xrpl/tx/wasm/detail/CodecTypeCode.h
Normal file
21
include/xrpl/tx/wasm/detail/CodecTypeCode.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/wasm/CodecEnvelope.h>
|
||||
|
||||
namespace xrpl::wasm::detail {
|
||||
|
||||
template <typename T>
|
||||
struct CodecTypeCodeOf;
|
||||
|
||||
// Defines a type trait for registering a type code for each
|
||||
// type in the codec.
|
||||
template <typename T>
|
||||
inline constexpr CodecTypeCode codecTypeCodeV = CodecTypeCodeOf<T>::kCodecTypeCode;
|
||||
|
||||
#define XRPL_WASM_CODEC_TYPE_CODE(CppType, Code) \
|
||||
template <> \
|
||||
struct CodecTypeCodeOf<CppType> { \
|
||||
static constexpr CodecTypeCode kCodecTypeCode = Code; \
|
||||
};
|
||||
|
||||
} // namespace xrpl::wasm::detail
|
||||
27
include/xrpl/tx/wasm/detail/CodecTypeTrait.h
Normal file
27
include/xrpl/tx/wasm/detail/CodecTypeTrait.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/wasm/CodecEnvelope.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace xrpl::wasm::detail {
|
||||
|
||||
// Defines a type trait for registering a specific type code and version
|
||||
// encoding/decoding implementation for each type in the codec.
|
||||
template <CodecTypeCode TypeCode, CodecVersion Version>
|
||||
struct CodecTypeTrait;
|
||||
|
||||
#define XRPL_WASM_CODEC_TYPE_TRAIT(CppType, Version, PayloadSize) \
|
||||
template <> \
|
||||
struct CodecTypeTrait<codecTypeCodeV<CppType>, Version> { \
|
||||
using CodecType = CppType; \
|
||||
static constexpr auto kCodecTypeCode = codecTypeCodeV<CppType>; \
|
||||
static constexpr auto kVersion = Version; \
|
||||
static constexpr auto kPayloadSize = PayloadSize; \
|
||||
static Expected<CodecType, CodecError> decode(CodecEnvelope const& env); \
|
||||
static Expected<CodecEnvelope, CodecError> encode(CodecType const& value); \
|
||||
};
|
||||
|
||||
inline constexpr auto kVariablePayloadSize = std::numeric_limits<CodecLength>::max();
|
||||
|
||||
} // namespace xrpl::wasm::detail
|
||||
48
include/xrpl/tx/wasm/detail/codec_revisions.macro
Normal file
48
include/xrpl/tx/wasm/detail/codec_revisions.macro
Normal file
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// codec_revisions.macro — the SINGLE registration point for codec
|
||||
// REVISIONS (the "contract-declared codec revision -> per-type wire version"
|
||||
// pinning). Included multiple times with different macro definitions, so NO
|
||||
// include guard.
|
||||
//
|
||||
// A revision fixes, for every type, which wire version a contract that declared
|
||||
// that revision receives. It is deliberately SEPARATE from the (type, version)
|
||||
// registry: adding a codec version does not change any revision — bumping a
|
||||
// revision is its own act, tied to the amendment that enables it.
|
||||
//
|
||||
// CODEC_REVISION_BEGIN(Revision)
|
||||
// CODEC_REVISION_ENTRY(CppType, Version) // repeated, one per type
|
||||
// CODEC_REVISION_END(Revision)
|
||||
//
|
||||
// HOW TO ADD A REVISION (e.g. revision 1 that emits the widened Number v1):
|
||||
// 1. Add a block here:
|
||||
// CODEC_REVISION_BEGIN(1)
|
||||
// CODEC_REVISION_ENTRY(Number, 1) // widened
|
||||
// CODEC_REVISION_ENTRY(AccountID, 0) // unchanged
|
||||
// CODEC_REVISION_END(1)
|
||||
// 2. Gate it behind its amendment in CodecRevisions.cpp (see the
|
||||
// amendment-gating TODO in wireVersionFor).
|
||||
// Both CodecTrait<1> and the wireVersionFor switch case regenerate from this.
|
||||
//
|
||||
|
||||
#ifndef CODEC_REVISION_BEGIN
|
||||
#define CODEC_REVISION_BEGIN(Revision)
|
||||
#endif
|
||||
#ifndef CODEC_REVISION_ENTRY
|
||||
#define CODEC_REVISION_ENTRY(CppType, Version)
|
||||
#endif
|
||||
#ifndef CODEC_REVISION_END
|
||||
#define CODEC_REVISION_END(Revision)
|
||||
#endif
|
||||
|
||||
// clang-format off
|
||||
|
||||
CODEC_REVISION_BEGIN(0)
|
||||
CODEC_REVISION_ENTRY(Number, 0)
|
||||
CODEC_REVISION_ENTRY(AccountID, 0)
|
||||
CODEC_REVISION_END(0)
|
||||
|
||||
// clang-format on
|
||||
|
||||
#undef CODEC_REVISION_BEGIN
|
||||
#undef CODEC_REVISION_ENTRY
|
||||
#undef CODEC_REVISION_END
|
||||
58
include/xrpl/tx/wasm/detail/codec_types.macro
Normal file
58
include/xrpl/tx/wasm/detail/codec_types.macro
Normal file
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// codec_types.macro — the SINGLE registration point for wasm codecs.
|
||||
//
|
||||
// This file is #included multiple times with different macro definitions, so
|
||||
// it has NO include guard. Every codec is declared here exactly once, on two
|
||||
// axes, and everything else (the trait tuple, the dispatch tables, the
|
||||
// explicit instantiations, the type-code map) is generated from it — so those
|
||||
// can never drift apart.
|
||||
//
|
||||
// CODEC_TYPE(CppType, TypeCode)
|
||||
// Once per C++ type. Assigns its stable 16-bit wire type code. Drives the
|
||||
// type-code map and the per-type explicit instantiations of decode/encode.
|
||||
//
|
||||
// CODEC_VERSION(CppType, Version, PayloadSize)
|
||||
// Once per (type, version). Declares a CodecTypeTrait specialization and
|
||||
// adds it to the registered-codec tuple. Versions per type MUST be
|
||||
// contiguous from 0 (enforced at compile time).
|
||||
//
|
||||
// HOW TO ADD A CODEC
|
||||
//
|
||||
// A) A new VERSION of an existing type (e.g. widen Number):
|
||||
// 1. Add a line here: CODEC_VERSION(Number, 1, 20)
|
||||
// 2. Define its bodies in the type's .cpp (codecs/NumberCodec.cpp):
|
||||
// CodecTypeTrait<codecTypeCodeV<Number>, 1>::decode/encode
|
||||
// 3. If a codec revision should emit it, add an entry in
|
||||
// codec_revisions.macro.
|
||||
// (Versions must stay contiguous from 0, or the build fails.)
|
||||
//
|
||||
// B) A brand-new TYPE (e.g. STAmount):
|
||||
// 1. CODEC_TYPE(STAmount, 0x0003) and CODEC_VERSION(STAmount, 0, N) here.
|
||||
// 2. Add its C++ header include to codecs/CodecTypes.h.
|
||||
// 3. Create codecs/STAmountCodec.cpp with the decode/encode bodies.
|
||||
// (GLOB_RECURSE picks up the new .cpp automatically — no CMake edit.)
|
||||
// 4. Add a CODEC_REVISION_ENTRY(STAmount, 0) to each relevant revision
|
||||
// block in codec_revisions.macro.
|
||||
//
|
||||
|
||||
#ifndef CODEC_TYPE
|
||||
#define CODEC_TYPE(CppType, TypeCode)
|
||||
#endif
|
||||
#ifndef CODEC_VERSION
|
||||
#define CODEC_VERSION(CppType, Version, PayloadSize)
|
||||
#endif
|
||||
|
||||
// clang-format off
|
||||
|
||||
// C++ type wire type code
|
||||
CODEC_TYPE(Number, 0x0001)
|
||||
CODEC_TYPE(AccountID, 0x0002)
|
||||
|
||||
// C++ type version payload bytes
|
||||
CODEC_VERSION(Number, 0, 12) // int64 mantissa + int32 exponent
|
||||
CODEC_VERSION(AccountID, 0, 20) // 20 raw bytes
|
||||
|
||||
// clang-format on
|
||||
|
||||
#undef CODEC_TYPE
|
||||
#undef CODEC_VERSION
|
||||
38
src/libxrpl/tx/wasm/codecs/AccountIDCodec.cpp
Normal file
38
src/libxrpl/tx/wasm/codecs/AccountIDCodec.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#include "CodecTypes.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
// Codec bodies for AccountID. One decode/encode pair per registered version.
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
|
||||
namespace xrpl::wasm::detail {
|
||||
|
||||
// ── AccountID, version 0 : 20 raw bytes ─────────────────────────────────────
|
||||
|
||||
template <>
|
||||
Expected<AccountID, CodecError>
|
||||
CodecTypeTrait<codecTypeCodeV<AccountID>, 0>::decode(CodecEnvelope const& env)
|
||||
{
|
||||
if (env.header.payload_size != kPayloadSize)
|
||||
{
|
||||
return Unexpected{CodecError::Truncated};
|
||||
}
|
||||
|
||||
auto id = AccountID{};
|
||||
std::memcpy(id.data(), env.payload, id.size());
|
||||
return id;
|
||||
}
|
||||
|
||||
template <>
|
||||
Expected<CodecEnvelope, CodecError>
|
||||
CodecTypeTrait<codecTypeCodeV<AccountID>, 0>::encode(AccountID const& value)
|
||||
{
|
||||
// AccountID is fixed-width, so it always fits v0 (no NotRepresentable path).
|
||||
// TODO(payload-ownership): payload bytes (value.data(), value.size()) must
|
||||
// be owned by the wire layer; deferred per the current encode signature.
|
||||
(void)value;
|
||||
return CodecEnvelope{.header={.type_code=kCodecTypeCode, .version=kVersion, .payload_size=kPayloadSize}, .payload=nullptr};
|
||||
}
|
||||
|
||||
} // namespace xrpl::wasm::detail
|
||||
60
src/libxrpl/tx/wasm/codecs/CodecRegistry.cpp
Normal file
60
src/libxrpl/tx/wasm/codecs/CodecRegistry.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#include <xrpl/tx/wasm/Codec.h>
|
||||
#include <xrpl/tx/wasm/detail/CodecDispatch.h>
|
||||
#include <xrpl/tx/wasm/detail/CodecList.h>
|
||||
#include <xrpl/tx/wasm/detail/CodecRevisions.h>
|
||||
|
||||
#include "CodecTypes.h"
|
||||
|
||||
//
|
||||
// The registry translation unit. This is the ONE place that:
|
||||
// * assembles the complete registered-codec tuple from the .def, and
|
||||
// * explicitly instantiates decode<T>/encode<T> for every registered type.
|
||||
//
|
||||
// Because the definitions live here (not in the public header), callers get the
|
||||
// codec machinery out of their compiles, and an unregistered decode<T>/encode<T>
|
||||
// fails at link time rather than with a template error.
|
||||
//
|
||||
|
||||
namespace xrpl::wasm {
|
||||
|
||||
namespace {
|
||||
|
||||
// The single enumeration of every registered codec, built straight from the
|
||||
// .def. The trailing detail::CodecEnd absorbs the final comma of the expansion.
|
||||
using RegisteredCodecs = detail::CodecList<
|
||||
#define CODEC_VERSION(CppType, Version, PayloadSize) \
|
||||
detail::CodecTypeTrait<detail::codecTypeCodeV<CppType>, Version>,
|
||||
#include <xrpl/tx/wasm/detail/codec_types.macro>
|
||||
detail::CodecEnd>::TupleType;
|
||||
|
||||
} // namespace
|
||||
|
||||
template <typename T>
|
||||
Expected<T, CodecError>
|
||||
decode(CodecEnvelope const& env)
|
||||
{
|
||||
return detail::decodeImpl<T, RegisteredCodecs>(env);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Expected<CodecEnvelope, CodecError>
|
||||
encode(CodecVersion declaredRevision, T const& value)
|
||||
{
|
||||
auto const wire =
|
||||
detail::wireVersionFor(declaredRevision, detail::codecTypeCodeV<T>);
|
||||
if (!wire)
|
||||
{
|
||||
return Unexpected{wire.error()};
|
||||
}
|
||||
return detail::encodeAt<T, RegisteredCodecs>(*wire, value);
|
||||
}
|
||||
|
||||
// Explicit instantiation for every registered type — the closed set.
|
||||
#define CODEC_TYPE(CppType, TypeCode) \
|
||||
template Expected<CppType, CodecError> decode<CppType>( \
|
||||
CodecEnvelope const&); \
|
||||
template Expected<CodecEnvelope, CodecError> encode<CppType>( \
|
||||
CodecVersion, CppType const&);
|
||||
#include <xrpl/tx/wasm/detail/codec_types.macro>
|
||||
|
||||
} // namespace xrpl::wasm
|
||||
74
src/libxrpl/tx/wasm/codecs/CodecRevisions.cpp
Normal file
74
src/libxrpl/tx/wasm/codecs/CodecRevisions.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#include <xrpl/tx/wasm/detail/CodecRevisions.h>
|
||||
#include <xrpl/tx/wasm/detail/CodecTrait.h>
|
||||
|
||||
#include "CodecTypes.h"
|
||||
|
||||
#include <array>
|
||||
#include <utility>
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
// Revision / amendment POLICY.
|
||||
//
|
||||
// The CodecTrait<N> revision maps and the wireVersionFor switch are BOTH
|
||||
// generated from codec_revisions.macro, exactly as the (type, version)
|
||||
// registry drives the dispatch tables — so the maps and the dispatch can't
|
||||
// drift from the registration list.
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
|
||||
namespace xrpl::wasm::detail {
|
||||
|
||||
// (1) Generate the CodecTrait<N> revision maps.
|
||||
#define CODEC_REVISION_BEGIN(Revision) \
|
||||
template <> \
|
||||
struct CodecTrait<Revision> \
|
||||
{ \
|
||||
static constexpr CodecVersion kVersion = Revision; \
|
||||
static constexpr auto versions = \
|
||||
std::to_array<std::pair<CodecTypeCode, CodecVersion>>({
|
||||
#define CODEC_REVISION_ENTRY(CppType, Version) \
|
||||
{codecTypeCodeV<CppType>, Version},
|
||||
#define CODEC_REVISION_END(Revision) \
|
||||
}); \
|
||||
};
|
||||
#include <xrpl/tx/wasm/detail/codec_revisions.macro>
|
||||
|
||||
namespace {
|
||||
|
||||
template <CodecVersion N>
|
||||
Expected<CodecVersion, CodecError>
|
||||
versionFromRevision(CodecTypeCode code)
|
||||
{
|
||||
for (auto const& [c, v] : CodecTrait<N>::versions)
|
||||
{
|
||||
if (c == code)
|
||||
{
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return Unexpected{CodecError::TypeNotInRevision};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Expected<CodecVersion, CodecError>
|
||||
wireVersionFor(CodecVersion declaredRevision, CodecTypeCode code)
|
||||
{
|
||||
// TODO(amendment-gating): each revision > 0 must additionally be gated
|
||||
// behind its amendment (via Rules) before a node may emit it, so no node
|
||||
// emits a wire version its peers can't agree on. Thread Rules in here.
|
||||
|
||||
// (2) Generate the runtime-revision -> CodecTrait<N> dispatch.
|
||||
switch (declaredRevision)
|
||||
{
|
||||
#define CODEC_REVISION_BEGIN(Revision) \
|
||||
case Revision: \
|
||||
return versionFromRevision<Revision>(code);
|
||||
#define CODEC_REVISION_ENTRY(CppType, Version)
|
||||
#define CODEC_REVISION_END(Revision)
|
||||
#include <xrpl/tx/wasm/detail/codec_revisions.macro>
|
||||
default:
|
||||
return Unexpected{CodecError::UnknownCodecVersion};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace xrpl::wasm::detail
|
||||
34
src/libxrpl/tx/wasm/codecs/CodecTypes.h
Normal file
34
src/libxrpl/tx/wasm/codecs/CodecTypes.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
//
|
||||
// Internal aggregation header for the wasm codec registry (NOT public — lives
|
||||
// under src/). Expands codec_types.macro to declare, in one shared place:
|
||||
//
|
||||
// * CODEC_TYPE -> the C++ type -> CodecTypeCode mapping
|
||||
// * CODEC_VERSION -> each (type, version) CodecTypeTrait specialization
|
||||
//
|
||||
// Both the registry TU and every per-type codec .cpp include this, so the trait
|
||||
// declarations they share are always generated from the same .def and can never
|
||||
// drift.
|
||||
//
|
||||
|
||||
#include <xrpl/basics/Expected.h> // used by the trait macro's decode/encode
|
||||
#include <xrpl/tx/wasm/detail/CodecTypeCode.h>
|
||||
#include <xrpl/tx/wasm/detail/CodecTypeTrait.h>
|
||||
|
||||
// One include per registered C++ *type* (not per version):
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
|
||||
namespace xrpl::wasm::detail {
|
||||
|
||||
// type -> type code
|
||||
#define CODEC_TYPE(CppType, TypeCode) XRPL_WASM_CODEC_TYPE_CODE(CppType, TypeCode)
|
||||
#include <xrpl/tx/wasm/detail/codec_types.macro>
|
||||
|
||||
// per-(type, version) trait declarations (bodies live in the type's .cpp)
|
||||
#define CODEC_VERSION(CppType, Version, PayloadSize) \
|
||||
XRPL_WASM_CODEC_TYPE_TRAIT(CppType, Version, PayloadSize)
|
||||
#include <xrpl/tx/wasm/detail/codec_types.macro>
|
||||
|
||||
} // namespace xrpl::wasm::detail
|
||||
54
src/libxrpl/tx/wasm/codecs/NumberCodec.cpp
Normal file
54
src/libxrpl/tx/wasm/codecs/NumberCodec.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#include "CodecTypes.h"
|
||||
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
// Codec bodies for Number. One decode/encode pair per registered version.
|
||||
// (GLOB_RECURSE compiles this file automatically; it is wired into the dispatch
|
||||
// tables by the explicit instantiations in CodecRegistry.cpp.)
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
|
||||
namespace xrpl::wasm::detail {
|
||||
|
||||
// ── Number, version 0 : int64 mantissa + int32 exponent = 12 bytes ──────────
|
||||
// Layout deliberately matches the frozen on-ledger STNumber serialization.
|
||||
|
||||
template <>
|
||||
Expected<Number, CodecError>
|
||||
CodecTypeTrait<codecTypeCodeV<Number>, 0>::decode(CodecEnvelope const& env)
|
||||
{
|
||||
if (env.header.payload_size != kPayloadSize)
|
||||
{
|
||||
return Unexpected{CodecError::Truncated};
|
||||
}
|
||||
|
||||
auto sit = SerialIter{env.payload, env.header.payload_size};
|
||||
auto const mantissa = sit.geti64();
|
||||
auto const exponent = sit.geti32();
|
||||
return Number{mantissa, exponent};
|
||||
}
|
||||
|
||||
template <>
|
||||
Expected<CodecEnvelope, CodecError>
|
||||
CodecTypeTrait<codecTypeCodeV<Number>, 0>::encode(Number const& value)
|
||||
{
|
||||
// v0 carries an int64 mantissa; Number's mantissa is int64 today so it
|
||||
// always fits. The guard documents the boundary for a future wider Number
|
||||
// (the "graceful fail" path when a value can't be represented in v0).
|
||||
if (value.exponent() < std::numeric_limits<std::int32_t>::min() ||
|
||||
value.exponent() > std::numeric_limits<std::int32_t>::max())
|
||||
{
|
||||
return Unexpected{CodecError::NotRepresentable};
|
||||
}
|
||||
|
||||
// TODO(payload-ownership): the returned envelope's payload must reference
|
||||
// storage that outlives the call; that ownership belongs to the wire layer.
|
||||
// The encode signature is intentionally left as-is for now, so the actual
|
||||
// byte production is deferred there.
|
||||
return CodecEnvelope{.header={.type_code=kCodecTypeCode, .version=kVersion, .payload_size=kPayloadSize}, .payload=nullptr};
|
||||
}
|
||||
|
||||
} // namespace xrpl::wasm::detail
|
||||
Reference in New Issue
Block a user