diff --git a/.clang-tidy b/.clang-tidy index 5a1ba7c321..88dd6f4e57 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -9,37 +9,24 @@ Checks: "-*, cppcoreguidelines-*, -cppcoreguidelines-avoid-c-arrays, - -cppcoreguidelines-avoid-capturing-lambda-coroutines, -cppcoreguidelines-avoid-const-or-ref-data-members, -cppcoreguidelines-avoid-do-while, - -cppcoreguidelines-avoid-goto, -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-avoid-non-const-global-variables, - -cppcoreguidelines-avoid-reference-coroutine-parameters, -cppcoreguidelines-c-copy-assignment-signature, - -cppcoreguidelines-explicit-virtual-functions, -cppcoreguidelines-interfaces-global-init, - -cppcoreguidelines-macro-to-enum, -cppcoreguidelines-macro-usage, -cppcoreguidelines-missing-std-forward, -cppcoreguidelines-narrowing-conversions, - -cppcoreguidelines-no-malloc, - -cppcoreguidelines-noexcept-destructor, -cppcoreguidelines-noexcept-move-operations, - -cppcoreguidelines-noexcept-swap, -cppcoreguidelines-non-private-member-variables-in-classes, -cppcoreguidelines-owning-memory, - -cppcoreguidelines-prefer-member-initializer, -cppcoreguidelines-pro-bounds-array-to-pointer-decay, -cppcoreguidelines-pro-bounds-avoid-unchecked-container-access, -cppcoreguidelines-pro-bounds-constant-array-index, -cppcoreguidelines-pro-bounds-pointer-arithmetic, - -cppcoreguidelines-pro-type-const-cast, - -cppcoreguidelines-pro-type-cstyle-cast, -cppcoreguidelines-pro-type-reinterpret-cast, -cppcoreguidelines-pro-type-union-access, - -cppcoreguidelines-pro-type-vararg, - -cppcoreguidelines-slicing, -cppcoreguidelines-special-member-functions, llvm-namespace-comment, @@ -63,13 +50,7 @@ Checks: "-*, performance-*, -performance-avoid-endl, -performance-enum-size, - -performance-inefficient-algorithm, - -performance-inefficient-string-concatenation, - -performance-no-int-to-ptr, - -performance-noexcept-destructor, -performance-noexcept-move-constructor, - -performance-noexcept-swap, - -performance-type-promotion-in-math-fn, -performance-unnecessary-copy-initialization, -performance-unnecessary-value-param, diff --git a/.cspell.config.yaml b/.cspell.config.yaml index c558fc0984..dc24bf833b 100644 --- a/.cspell.config.yaml +++ b/.cspell.config.yaml @@ -279,6 +279,8 @@ words: - sles - soci - socidb + - sponsee + - sponsees - SRPMS - sslws - statsd @@ -327,6 +329,7 @@ words: - unserviced - unshareable - unshares + - unsponsored - unsquelch - unsquelched - unsquelching diff --git a/.envrc b/.envrc new file mode 100644 index 0000000000..3550a30f2d --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.github/scripts/levelization/results/ordering.txt b/.github/scripts/levelization/results/ordering.txt index 5cc49dd2f5..aee6f4c579 100644 --- a/.github/scripts/levelization/results/ordering.txt +++ b/.github/scripts/levelization/results/ordering.txt @@ -72,7 +72,6 @@ test.app > xrpl.server test.app > xrpl.shamap test.app > xrpl.tx test.basics > test.jtx -test.basics > test.unit_test test.basics > xrpl.basics test.basics > xrpl.core test.basics > xrpld.rpc @@ -162,9 +161,6 @@ test.protocol > test.unit_test test.protocol > xrpl.basics test.protocol > xrpl.json test.protocol > xrpl.protocol -test.resource > test.unit_test -test.resource > xrpl.basics -test.resource > xrpl.resource test.rpc > test.jtx test.rpc > xrpl.basics test.rpc > xrpl.config @@ -188,12 +184,6 @@ test.server > xrpld.core test.server > xrpl.json test.server > xrpl.protocol test.server > xrpl.server -test.shamap > test.unit_test -test.shamap > xrpl.basics -test.shamap > xrpl.config -test.shamap > xrpl.nodestore -test.shamap > xrpl.protocol -test.shamap > xrpl.shamap test.unit_test > xrpl.basics test.unit_test > xrpl.protocol tests.libxrpl > xrpl.basics @@ -205,6 +195,7 @@ tests.libxrpl > xrpl.net tests.libxrpl > xrpl.nodestore tests.libxrpl > xrpl.protocol tests.libxrpl > xrpl.protocol_autogen +tests.libxrpl > xrpl.resource tests.libxrpl > xrpl.server tests.libxrpl > xrpl.shamap tests.libxrpl > xrpl.tx diff --git a/cmake/scripts/codegen/templates/LedgerEntry.h.mako b/cmake/scripts/codegen/templates/LedgerEntry.h.mako index 63f5f39ef9..c799903b21 100644 --- a/cmake/scripts/codegen/templates/LedgerEntry.h.mako +++ b/cmake/scripts/codegen/templates/LedgerEntry.h.mako @@ -177,7 +177,9 @@ ${field['typeData']['setter_type']} ${field['paramName']}${',' if i < len(requir object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ % for field in fields: /** diff --git a/cmake/scripts/codegen/templates/Transaction.h.mako b/cmake/scripts/codegen/templates/Transaction.h.mako index d3b303d9d6..49e2e4a5cd 100644 --- a/cmake/scripts/codegen/templates/Transaction.h.mako +++ b/cmake/scripts/codegen/templates/Transaction.h.mako @@ -185,7 +185,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ % for field in fields: /** diff --git a/docs/build/environment.md b/docs/build/environment.md index 2cca608567..e639ed2d5f 100644 --- a/docs/build/environment.md +++ b/docs/build/environment.md @@ -33,9 +33,10 @@ with a single command and without installing anything system-wide: nix --experimental-features 'nix-command flakes' develop ``` -On **Linux**, Nix also provides the compiler (GCC). On **macOS**, the shell uses -your **system-wide Apple Clang** as the compiler, so you still need to manage -its version (see below). +On **Linux**, Nix also provides the compiler (GCC); on **macOS**, it provides +Clang. If you instead opt to use your system-wide Apple Clang (via +`nix develop .#apple-clang`), you need to manage its version yourself (see +below). See [Using the Nix development shell](./nix.md) for installation and usage details, including how to select a different compiler. @@ -48,10 +49,10 @@ details, including how to select a different compiler. ### macOS: managing the Apple Clang version -Because the Nix shell uses the system-wide Apple Clang on macOS, the compiler -version is whatever your installed Xcode (or Command Line Tools) provides. The -following command should return a version greater than or equal to the -[minimum required](#tested-compiler-versions): +If you use your system-wide Apple Clang on macOS (via `nix develop .#apple-clang`), +the compiler version is whatever your installed Xcode (or Command Line Tools) +provides. The following command should return a version greater than or equal to +the [minimum required](#tested-compiler-versions): ```bash clang --version diff --git a/docs/build/nix.md b/docs/build/nix.md index 2ae483aefe..d6e53a254a 100644 --- a/docs/build/nix.md +++ b/docs/build/nix.md @@ -9,7 +9,7 @@ This guide explains how to use Nix to set up a reproducible development environm - **Reproducible environment**: Everyone gets the same versions of tools and compilers - **Matches CI**: The Linux CI runs in Docker images built from this exact Nix environment - **No system pollution**: Dependencies are isolated and don't affect your system packages -- **Multiple compiler versions**: Easily switch between different GCC and Clang versions +- **Consistent compilers**: The GCC and Clang shells use the same versions as CI - **Quick setup**: Get started with a single command - **Works on Linux and macOS**: Consistent experience across platforms @@ -31,8 +31,8 @@ This will: - Download and set up all required development tools (CMake, Ninja, Conan, etc.) - Configure the appropriate compiler for your platform: - - **Linux**: GCC 15.2 (provided by Nix) - - **macOS**: Apple Clang (your system compiler) + - **Linux**: GCC (provided by Nix) + - **macOS**: Clang (provided by Nix) The first time you run this command, it will take a few minutes to download and build the environment. Subsequent runs will be much faster. @@ -40,12 +40,12 @@ The first time you run this command, it will take a few minutes to download and - **Linux**: `nix develop` gives you a shell with all the tooling necessary to develop xrpld and with GCC 15.2 (also provided by Nix). There are no caveats. -- **macOS**: `nix develop` gives you a full environment too. The compiler is - your system-wide Apple Clang, while every other tool — including Conan — is - provided by Nix. Conan has no binary in the Nix cache for macOS, so it is - built from source the first time you enter the shell, which makes the initial - setup slower (this is handled automatically; see - [`nix/devshell.nix`](../../nix/devshell.nix)). +- **macOS**: `nix develop` gives you a full environment too, with Clang (and + every other tool, including Conan) provided by Nix. To use your system-wide + Apple Clang instead, enter `nix develop .#apple-clang`. Conan has no binary in + the Nix cache for macOS, so it is built from source the first time you enter + the shell, which makes the initial setup slower (this is handled + automatically; see [`nix/devshell.nix`](../../nix/devshell.nix)). > [!TIP] > To avoid typing `--experimental-features 'nix-command flakes'` every time, you can permanently enable flakes by creating `~/.config/nix/nix.conf`: @@ -62,7 +62,9 @@ The first time you run this command, it will take a few minutes to download and ### Choosing a different compiler -A compiler can be chosen by providing its name with the `.#` prefix, e.g. `nix develop .#gcc15`. +A compiler can be chosen by providing its name with the `.#` prefix, e.g. `nix develop .#clang`. +The `.#gcc` and `.#clang` shells provide the same GCC and Clang versions used in CI +(pinned in [`nix/packages.nix`](../../nix/packages.nix)). Use `nix flake show` to see all the available development shells. Use `nix develop .#no-compiler` to use the compiler from your system. @@ -70,11 +72,11 @@ Use `nix develop .#no-compiler` to use the compiler from your system. ### Example Usage ```bash -# Use GCC 14 -nix develop .#gcc14 +# Use GCC (same version as CI) +nix develop .#gcc -# Use Clang 19 -nix develop .#clang19 +# Use Clang (same version as CI) +nix develop .#clang # Use default for your platform nix develop @@ -112,7 +114,15 @@ Once inside the Nix development shell, follow the standard [build instructions]( [direnv](https://direnv.net/) or [nix-direnv](https://github.com/nix-community/nix-direnv) can automatically activate the Nix development shell when you enter the repository directory. -This is also the most robust way to use the environment from **any shell** (bash, zsh, fish, …): direnv stays in your current shell and loads the environment _after_ your shell's startup files have run, so the Nix-provided tools take precedence over anything your shell configuration adds to `$PATH`. To use it, install direnv for your shell, then add an `.envrc` containing `use flake` at the repository root and run `direnv allow`. +This is also the most robust way to use the environment from **any shell** (bash, zsh, fish, …): direnv stays in your current shell and loads the environment _after_ your shell's startup files have run, so the Nix-provided tools take precedence over anything your shell configuration adds to `$PATH`. + +The repository already ships an `.envrc` at its root that activates the Nix flake development shell, so you don't need to create one. To use it: + +1. [Install direnv](https://direnv.net/docs/installation.html) and [hook it into your shell](https://direnv.net/docs/hook.html) (bash, zsh, fish, …). Installing [nix-direnv](https://github.com/nix-community/nix-direnv) as well is recommended: it caches the shell so that activation is near-instant after the first run. +2. Run `direnv allow` once in the repository root. direnv will then load (and reload) the Nix development shell automatically whenever you enter the directory. + +> [!NOTE] +> direnv only caches the `.direnv` directory (already listed in `.gitignore`); no other repository files are affected. ## Conan and Prebuilt Packages diff --git a/include/xrpl/basics/Archive.h b/include/xrpl/basics/Archive.h index 58e12bbb71..66d6a019af 100644 --- a/include/xrpl/basics/Archive.h +++ b/include/xrpl/basics/Archive.h @@ -4,13 +4,14 @@ namespace xrpl { -/** Extract a tar archive compressed with lz4 - - @param src the path of the archive to be extracted - @param dst the directory to extract to - - @throws runtime_error -*/ +/** + * Extract a tar archive compressed with lz4 + * + * @param src the path of the archive to be extracted + * @param dst the directory to extract to + * + * @throws runtime_error + */ void extractTarLz4(boost::filesystem::path const& src, boost::filesystem::path const& dst); diff --git a/include/xrpl/basics/Blob.h b/include/xrpl/basics/Blob.h index ee0d6cf3b5..bfb8e5a697 100644 --- a/include/xrpl/basics/Blob.h +++ b/include/xrpl/basics/Blob.h @@ -4,9 +4,10 @@ namespace xrpl { -/** Storage for linear binary data. - Blocks of binary data appear often in various idioms and structures. -*/ +/** + * Storage for linear binary data. + * Blocks of binary data appear often in various idioms and structures. + */ using Blob = std::vector; } // namespace xrpl diff --git a/include/xrpl/basics/Buffer.h b/include/xrpl/basics/Buffer.h index c0ae8ef56e..05af6c409a 100644 --- a/include/xrpl/basics/Buffer.h +++ b/include/xrpl/basics/Buffer.h @@ -10,9 +10,10 @@ namespace xrpl { -/** Like std::vector but better. - Meets the requirements of BufferFactory. -*/ +/** + * Like std::vector but better. + * Meets the requirements of BufferFactory. + */ class Buffer { private: @@ -24,30 +25,37 @@ public: Buffer() = default; - /** Create an uninitialized buffer with the given size. */ + /** + * Create an uninitialized buffer with the given size. + */ explicit Buffer(std::size_t size) : p_((size != 0u) ? new std::uint8_t[size] : nullptr), size_(size) { } - /** Create a buffer as a copy of existing memory. - - @param data a pointer to the existing memory. If - size is non-zero, it must not be null. - @param size size of the existing memory block. - */ + /** + * Create a buffer as a copy of existing memory. + * + * @param data a pointer to the existing memory. If + * size is non-zero, it must not be null. + * @param size size of the existing memory block. + */ Buffer(void const* data, std::size_t size) : Buffer(size) { if (size != 0u) std::memcpy(p_.get(), data, size); } - /** Copy-construct */ + /** + * Copy-construct + */ Buffer(Buffer const& other) : Buffer(other.p_.get(), other.size_) { } - /** Copy assign */ + /** + * Copy assign + */ Buffer& operator=(Buffer const& other) { @@ -59,17 +67,19 @@ public: return *this; } - /** Move-construct. - The other buffer is reset. - */ + /** + * Move-construct. + * The other buffer is reset. + */ Buffer(Buffer&& other) noexcept : p_(std::move(other.p_)), size_(other.size_) { other.size_ = 0; } - /** Move-assign. - The other buffer is reset. - */ + /** + * Move-assign. + * The other buffer is reset. + */ Buffer& operator=(Buffer&& other) noexcept { @@ -82,12 +92,16 @@ public: return *this; } - /** Construct from a slice */ + /** + * Construct from a slice + */ explicit Buffer(Slice s) : Buffer(s.data(), s.size()) { } - /** Assign from slice */ + /** + * Assign from slice + */ Buffer& operator=(Slice s) { @@ -101,7 +115,9 @@ public: return *this; } - /** Returns the number of bytes in the buffer. */ + /** + * Returns the number of bytes in the buffer. + */ [[nodiscard]] std::size_t size() const noexcept { @@ -121,10 +137,11 @@ public: return Slice{p_.get(), size_}; } - /** Return a pointer to beginning of the storage. - @note The return type is guaranteed to be a pointer - to a single byte, to facilitate pointer arithmetic. - */ + /** + * Return a pointer to beginning of the storage. + * @note The return type is guaranteed to be a pointer + * to a single byte, to facilitate pointer arithmetic. + */ /** @{ */ [[nodiscard]] std::uint8_t const* data() const noexcept @@ -139,9 +156,10 @@ public: } /** @} */ - /** Reset the buffer. - All memory is deallocated. The resulting size is 0. - */ + /** + * Reset the buffer. + * All memory is deallocated. The resulting size is 0. + */ void clear() noexcept { @@ -149,9 +167,10 @@ public: size_ = 0; } - /** Reallocate the storage. - Existing data, if any, is discarded. - */ + /** + * Reallocate the storage. + * Existing data, if any, is discarded. + */ std::uint8_t* alloc(std::size_t n) { diff --git a/include/xrpl/basics/CompressionAlgorithms.h b/include/xrpl/basics/CompressionAlgorithms.h index a5ec8645b6..316acb14ac 100644 --- a/include/xrpl/basics/CompressionAlgorithms.h +++ b/include/xrpl/basics/CompressionAlgorithms.h @@ -12,7 +12,8 @@ namespace xrpl::compression_algorithms { -/** LZ4 block compression. +/** + * LZ4 block compression. * @tparam BufferFactory Callable object or lambda. * Takes the requested buffer size and returns allocated buffer pointer. * @param in Data to compress @@ -80,7 +81,8 @@ lz4Decompress( return decompressedSize; } -/** LZ4 block decompression. +/** + * LZ4 block decompression. * @tparam InputStream ZeroCopyInputStream * @param in Input source stream * @param inSize Size of compressed data diff --git a/include/xrpl/basics/CountedObject.h b/include/xrpl/basics/CountedObject.h index 275894673e..bb7b0d8877 100644 --- a/include/xrpl/basics/CountedObject.h +++ b/include/xrpl/basics/CountedObject.h @@ -9,7 +9,9 @@ namespace xrpl { -/** Manages all counted object types. */ +/** + * Manages all counted object types. + */ class CountedObjects { public: @@ -23,10 +25,11 @@ public: getCounts(int minimumThreshold) const; public: - /** Implementation for @ref CountedObject. - - @internal - */ + /** + * Implementation for @ref CountedObject. + * + * @internal + */ class Counter { public: @@ -94,13 +97,14 @@ private: //------------------------------------------------------------------------------ -/** Tracks the number of instances of an object. - - Derived classes have their instances counted automatically. This is used - for reporting purposes. - - @ingroup basics -*/ +/** + * Tracks the number of instances of an object. + * + * Derived classes have their instances counted automatically. This is used + * for reporting purposes. + * + * @ingroup basics + */ template class CountedObject { diff --git a/include/xrpl/basics/DecayingSample.h b/include/xrpl/basics/DecayingSample.h index 86a8baa62e..1b05770734 100644 --- a/include/xrpl/basics/DecayingSample.h +++ b/include/xrpl/basics/DecayingSample.h @@ -6,9 +6,10 @@ namespace xrpl { -/** Sampling function using exponential decay to provide a continuous value. - @tparam The number of seconds in the decay window. -*/ +/** + * Sampling function using exponential decay to provide a continuous value. + * @tparam The number of seconds in the decay window. + */ template class DecayingSample { @@ -19,15 +20,16 @@ public: DecayingSample() = delete; /** - @param now Start time of DecayingSample. - */ + * @param now Start time of DecayingSample. + */ explicit DecayingSample(time_point now) : value_(value_type()), when_(now) { } - /** Add a new sample. - The value is first aged according to the specified time. - */ + /** + * Add a new sample. + * The value is first aged according to the specified time. + */ value_type add(value_type value, time_point now) { @@ -36,9 +38,10 @@ public: return value_ / Window; } - /** Retrieve the current value in normalized units. - The samples are first aged according to the specified time. - */ + /** + * Retrieve the current value in normalized units. + * The samples are first aged according to the specified time. + */ value_type value(time_point now) { @@ -87,9 +90,10 @@ private: //------------------------------------------------------------------------------ -/** Sampling function using exponential decay to provide a continuous value. - @tparam HalfLife The half life of a sample, in seconds. -*/ +/** + * Sampling function using exponential decay to provide a continuous value. + * @tparam HalfLife The half life of a sample, in seconds. + */ template class DecayWindow { diff --git a/include/xrpl/basics/IntrusivePointer.h b/include/xrpl/basics/IntrusivePointer.h index c23d6afb85..59853ad4d0 100644 --- a/include/xrpl/basics/IntrusivePointer.h +++ b/include/xrpl/basics/IntrusivePointer.h @@ -10,33 +10,37 @@ namespace xrpl { //------------------------------------------------------------------------------ -/** Tag to create an intrusive pointer from another intrusive pointer by using a - static cast. This is useful to create an intrusive pointer to a derived - class from an intrusive pointer to a base class. -*/ +/** + * Tag to create an intrusive pointer from another intrusive pointer by using a + * static cast. This is useful to create an intrusive pointer to a derived + * class from an intrusive pointer to a base class. + */ struct StaticCastTagSharedIntrusive { }; -/** Tag to create an intrusive pointer from another intrusive pointer by using a - dynamic cast. This is useful to create an intrusive pointer to a derived - class from an intrusive pointer to a base class. If the cast fails an empty - (null) intrusive pointer is created. -*/ +/** + * Tag to create an intrusive pointer from another intrusive pointer by using a + * dynamic cast. This is useful to create an intrusive pointer to a derived + * class from an intrusive pointer to a base class. If the cast fails an empty + * (null) intrusive pointer is created. + */ struct DynamicCastTagSharedIntrusive { }; -/** When creating or adopting a raw pointer, controls whether the strong count - is incremented or not. Use this tag to increment the strong count. -*/ +/** + * When creating or adopting a raw pointer, controls whether the strong count + * is incremented or not. Use this tag to increment the strong count. + */ struct SharedIntrusiveAdoptIncrementStrongTag { }; -/** When creating or adopting a raw pointer, controls whether the strong count - is incremented or not. Use this tag to leave the strong count unchanged. -*/ +/** + * When creating or adopting a raw pointer, controls whether the strong count + * is incremented or not. Use this tag to leave the strong count unchanged. + */ struct SharedIntrusiveAdoptNoIncrementTag { }; @@ -50,20 +54,21 @@ concept CAdoptTag = std::is_same_v || //------------------------------------------------------------------------------ -/** A shared intrusive pointer class that supports weak pointers. - - This is meant to be used for SHAMapInnerNodes, but may be useful for other - cases. Since the reference counts are stored on the pointee, the pointee is - not destroyed until both the strong _and_ weak pointer counts go to zero. - When the strong pointer count goes to zero, the "partialDestructor" is - called. This can be used to destroy as much of the object as possible while - still retaining the reference counts. For example, for SHAMapInnerNodes the - children may be reset in that function. Note that std::shared_pointer WILL - run the destructor when the strong count reaches zero, but may not free the - memory used by the object until the weak count reaches zero. In xrpld, we - typically allocate shared pointers with the `make_shared` function. When - that is used, the memory is not reclaimed until the weak count reaches zero. -*/ +/** + * A shared intrusive pointer class that supports weak pointers. + * + * This is meant to be used for SHAMapInnerNodes, but may be useful for other + * cases. Since the reference counts are stored on the pointee, the pointee is + * not destroyed until both the strong _and_ weak pointer counts go to zero. + * When the strong pointer count goes to zero, the "partialDestructor" is + * called. This can be used to destroy as much of the object as possible while + * still retaining the reference counts. For example, for SHAMapInnerNodes the + * children may be reset in that function. Note that std::shared_pointer WILL + * run the destructor when the strong count reaches zero, but may not free the + * memory used by the object until the weak count reaches zero. In xrpld, we + * typically allocate shared pointers with the `make_shared` function. When + * that is used, the memory is not reclaimed until the weak count reaches zero. + */ template class SharedIntrusive { @@ -111,8 +116,9 @@ public: operator=( SharedIntrusive&& rhs); // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved) - /** Adopt the raw pointer. The strong reference may or may not be - incremented, depending on the TAdoptTag + /** + * Adopt the raw pointer. The strong reference may or may not be + * incremented, depending on the TAdoptTag */ template void @@ -120,27 +126,31 @@ public: ~SharedIntrusive(); - /** Create a new SharedIntrusive by statically casting the pointer - controlled by the rhs param. - */ + /** + * Create a new SharedIntrusive by statically casting the pointer + * controlled by the rhs param. + */ template SharedIntrusive(StaticCastTagSharedIntrusive, SharedIntrusive const& rhs); - /** Create a new SharedIntrusive by statically casting the pointer - controlled by the rhs param. - */ + /** + * Create a new SharedIntrusive by statically casting the pointer + * controlled by the rhs param. + */ template SharedIntrusive(StaticCastTagSharedIntrusive, SharedIntrusive&& rhs); - /** Create a new SharedIntrusive by dynamically casting the pointer - controlled by the rhs param. - */ + /** + * Create a new SharedIntrusive by dynamically casting the pointer + * controlled by the rhs param. + */ template SharedIntrusive(DynamicCastTagSharedIntrusive, SharedIntrusive const& rhs); - /** Create a new SharedIntrusive by dynamically casting the pointer - controlled by the rhs param. - */ + /** + * Create a new SharedIntrusive by dynamically casting the pointer + * controlled by the rhs param. + */ template SharedIntrusive(DynamicCastTagSharedIntrusive, SharedIntrusive&& rhs); @@ -153,17 +163,22 @@ public: explicit operator bool() const noexcept; - /** Set the pointer to null, decrement the strong count, and run the - appropriate release action. - */ + /** + * Set the pointer to null, decrement the strong count, and run the + * appropriate release action. + */ void reset(); - /** Get the raw pointer */ + /** + * Get the raw pointer + */ [[nodiscard]] T* get() const; - /** Return the strong count */ + /** + * Return the strong count + */ [[nodiscard]] std::size_t useCount() const; @@ -181,43 +196,51 @@ public: friend class WeakIntrusive; private: - /** Return the raw pointer held by this object. */ + /** + * Return the raw pointer held by this object. + */ [[nodiscard]] T* unsafeGetRawPtr() const; - /** Exchange the current raw pointer held by this object with the given - pointer. Decrement the strong count of the raw pointer previously held - by this object and run the appropriate release action. + /** + * Exchange the current raw pointer held by this object with the given + * pointer. Decrement the strong count of the raw pointer previously held + * by this object and run the appropriate release action. */ void unsafeReleaseAndStore(T* next); - /** Set the raw pointer directly. This is wrapped in a function so the class - can support both atomic and non-atomic pointers in a future patch. + /** + * Set the raw pointer directly. This is wrapped in a function so the class + * can support both atomic and non-atomic pointers in a future patch. */ void unsafeSetRawPtr(T* p); - /** Exchange the raw pointer directly. - This sets the raw pointer to the given value and returns the previous - value. This is wrapped in a function so the class can support both - atomic and non-atomic pointers in a future patch. + /** + * Exchange the raw pointer directly. + * This sets the raw pointer to the given value and returns the previous + * value. This is wrapped in a function so the class can support both + * atomic and non-atomic pointers in a future patch. */ T* unsafeExchange(T* p); - /** pointer to the type with an intrusive count */ + /** + * pointer to the type with an intrusive count + */ T* ptr_{nullptr}; }; //------------------------------------------------------------------------------ -/** A weak intrusive pointer class for the SharedIntrusive pointer class. - -Note that this weak pointer class asks differently from normal weak pointer -classes. When the strong pointer count goes to zero, the "partialDestructor" -is called. See the comment on SharedIntrusive for a fuller explanation. -*/ +/** + * A weak intrusive pointer class for the SharedIntrusive pointer class. + * + * Note that this weak pointer class asks differently from normal weak pointer + * classes. When the strong pointer count goes to zero, the "partialDestructor" + * is called. See the comment on SharedIntrusive for a fuller explanation. + */ template class WeakIntrusive { @@ -247,54 +270,62 @@ public: WeakIntrusive& operator=(SharedIntrusive const& rhs); - /** Adopt the raw pointer and increment the weak count. */ + /** + * Adopt the raw pointer and increment the weak count. + */ void adopt(T* ptr); ~WeakIntrusive(); - /** Get a strong pointer from the weak pointer, if possible. This will - only return a seated pointer if the strong count on the raw pointer - is non-zero before locking. + /** + * Get a strong pointer from the weak pointer, if possible. This will + * only return a seated pointer if the strong count on the raw pointer + * is non-zero before locking. */ SharedIntrusive lock() const; - /** Return true if the strong count is zero. */ + /** + * Return true if the strong count is zero. + */ [[nodiscard]] bool expired() const; - /** Set the pointer to null and decrement the weak count. - - Note: This may run the destructor if the strong count is zero. - */ + /** + * Set the pointer to null and decrement the weak count. + * + * Note: This may run the destructor if the strong count is zero. + */ void reset(); private: T* ptr_ = nullptr; - /** Decrement the weak count. This does _not_ set the raw pointer to - null. - - Note: This may run the destructor if the strong count is zero. - */ + /** + * Decrement the weak count. This does _not_ set the raw pointer to + * null. + * + * Note: This may run the destructor if the strong count is zero. + */ void unsafeReleaseNoStore(); }; //------------------------------------------------------------------------------ -/** A combination of a strong and a weak intrusive pointer stored in the - space of a single pointer. - - This class is similar to a `std::variant` - with some optimizations. In particular, it uses a low-order bit to - determine if the raw pointer represents a strong pointer or a weak - pointer. It can also be quickly switched between its strong pointer and - weak pointer representations. This class is useful for storing intrusive - pointers in tagged caches. - */ +/** + * A combination of a strong and a weak intrusive pointer stored in the + * space of a single pointer. + * + * This class is similar to a `std::variant` + * with some optimizations. In particular, it uses a low-order bit to + * determine if the raw pointer represents a strong pointer or a weak + * pointer. It can also be quickly switched between its strong pointer and + * weak pointer representations. This class is useful for storing intrusive + * pointers in tagged caches. + */ template class SharedWeakUnion @@ -336,69 +367,83 @@ public: ~SharedWeakUnion(); - /** Return a strong pointer if this is already a strong pointer (i.e. - don't lock the weak pointer. Use the `lock` method if that's what's - needed) + /** + * Return a strong pointer if this is already a strong pointer (i.e. + * don't lock the weak pointer. Use the `lock` method if that's what's + * needed) */ [[nodiscard]] SharedIntrusive getStrong() const; - /** Return true if this is a strong pointer and the strong pointer is - seated. + /** + * Return true if this is a strong pointer and the strong pointer is + * seated. */ explicit operator bool() const noexcept; - /** Set the pointer to null, decrement the appropriate ref count, and - run the appropriate release action. + /** + * Set the pointer to null, decrement the appropriate ref count, and + * run the appropriate release action. */ void reset(); - /** If this is a strong pointer, return the raw pointer. Otherwise - return null. + /** + * If this is a strong pointer, return the raw pointer. Otherwise + * return null. */ [[nodiscard]] T* get() const; - /** If this is a strong pointer, return the strong count. Otherwise + /** + * If this is a strong pointer, return the strong count. Otherwise * return 0 */ [[nodiscard]] std::size_t useCount() const; - /** Return true if there is a non-zero strong count. */ + /** + * Return true if there is a non-zero strong count. + */ [[nodiscard]] bool expired() const; - /** If this is a strong pointer, return the strong pointer. Otherwise - attempt to lock the weak pointer. + /** + * If this is a strong pointer, return the strong pointer. Otherwise + * attempt to lock the weak pointer. */ [[nodiscard]] SharedIntrusive lock() const; - /** Return true is this represents a strong pointer. */ + /** + * Return true is this represents a strong pointer. + */ [[nodiscard]] bool isStrong() const; - /** Return true is this represents a weak pointer. */ + /** + * Return true is this represents a weak pointer. + */ [[nodiscard]] bool isWeak() const; - /** If this is a weak pointer, attempt to convert it to a strong - pointer. - - @return true if successfully converted to a strong pointer (or was - already a strong pointer). Otherwise false. - */ + /** + * If this is a weak pointer, attempt to convert it to a strong + * pointer. + * + * @return true if successfully converted to a strong pointer (or was + * already a strong pointer). Otherwise false. + */ bool convertToStrong(); - /** If this is a strong pointer, attempt to convert it to a weak - pointer. - - @return false if the pointer is null. Otherwise return true. - */ + /** + * If this is a strong pointer, attempt to convert it to a weak + * pointer. + * + * @return false if the pointer is null. Otherwise return true. + */ bool convertToWeak(); @@ -411,23 +456,27 @@ private: static constexpr std::uintptr_t kPtrMask = ~kTagMask; private: - /** Return the raw pointer held by this object. + /** + * Return the raw pointer held by this object. */ [[nodiscard]] T* unsafeGetRawPtr() const; enum class RefStrength { Strong, Weak }; - /** Set the raw pointer and tag bit directly. + /** + * Set the raw pointer and tag bit directly. */ void unsafeSetRawPtr(T* p, RefStrength rs); - /** Set the raw pointer and tag bit to all zeros (strong null pointer). + /** + * Set the raw pointer and tag bit to all zeros (strong null pointer). */ void unsafeSetRawPtr(std::nullptr_t); - /** Decrement the appropriate ref count, and run the appropriate release - action. Note: this does _not_ set the raw pointer to null. + /** + * Decrement the appropriate ref count, and run the appropriate release + * action. Note: this does _not_ set the raw pointer to null. */ void unsafeReleaseNoStore(); @@ -435,12 +484,13 @@ private: //------------------------------------------------------------------------------ -/** Create a shared intrusive pointer. - - Note: unlike std::shared_ptr, where there is an advantage of allocating - the pointer and control block together, there is no benefit for intrusive - pointers. -*/ +/** + * Create a shared intrusive pointer. + * + * Note: unlike std::shared_ptr, where there is an advantage of allocating + * the pointer and control block together, there is no benefit for intrusive + * pointers. + */ template SharedIntrusive makeSharedIntrusive(Args&&... args) diff --git a/include/xrpl/basics/IntrusivePointer.ipp b/include/xrpl/basics/IntrusivePointer.ipp index 8344a3e613..67d43b05d6 100644 --- a/include/xrpl/basics/IntrusivePointer.ipp +++ b/include/xrpl/basics/IntrusivePointer.ipp @@ -641,6 +641,9 @@ template T* SharedWeakUnion::unsafeGetRawPtr() const { + // tp_ packs a raw pointer together with a strength bit; recovering the + // pointer inherently requires an integer-to-pointer cast. + // NOLINTNEXTLINE(performance-no-int-to-ptr) return reinterpret_cast(tp_ & kPtrMask); } diff --git a/include/xrpl/basics/IntrusiveRefCounts.h b/include/xrpl/basics/IntrusiveRefCounts.h index 5eb1422541..caa06ed786 100644 --- a/include/xrpl/basics/IntrusiveRefCounts.h +++ b/include/xrpl/basics/IntrusiveRefCounts.h @@ -8,35 +8,38 @@ namespace xrpl { -/** Action to perform when releasing a strong pointer. - - noop: Do nothing. For example, a `noop` action will occur when a count is - decremented to a non-zero value. - - partialDestroy: Run the `partialDestructor`. This action will happen when a - strong count is decremented to zero and the weak count is non-zero. - - destroy: Run the destructor. This action will occur when either the strong - count or weak count is decremented and the other count is also zero. +/** + * Action to perform when releasing a strong pointer. + * + * noop: Do nothing. For example, a `noop` action will occur when a count is + * decremented to a non-zero value. + * + * partialDestroy: Run the `partialDestructor`. This action will happen when a + * strong count is decremented to zero and the weak count is non-zero. + * + * destroy: Run the destructor. This action will occur when either the strong + * count or weak count is decremented and the other count is also zero. */ enum class ReleaseStrongRefAction { NoOp, PartialDestroy, Destroy }; -/** Action to perform when releasing a weak pointer. - - noop: Do nothing. For example, a `noop` action will occur when a count is - decremented to a non-zero value. - - destroy: Run the destructor. This action will occur when either the strong - count or weak count is decremented and the other count is also zero. +/** + * Action to perform when releasing a weak pointer. + * + * noop: Do nothing. For example, a `noop` action will occur when a count is + * decremented to a non-zero value. + * + * destroy: Run the destructor. This action will occur when either the strong + * count or weak count is decremented and the other count is also zero. */ enum class ReleaseWeakRefAction { NoOp, Destroy }; -/** Implement the strong count, weak count, and bit flags for an intrusive - pointer. - - A class can satisfy the requirements of an xrpl::IntrusivePointer by - inheriting from this class. - */ +/** + * Implement the strong count, weak count, and bit flags for an intrusive + * pointer. + * + * A class can satisfy the requirements of an xrpl::IntrusivePointer by + * inheriting from this class. + */ struct IntrusiveRefCounts { virtual ~IntrusiveRefCounts() noexcept; @@ -105,109 +108,123 @@ private: static constexpr size_t kFieldTypeBits = sizeof(FieldType) * 8; static constexpr FieldType kOne = 1; - /** `refCounts` consists of four fields that are treated atomically: - - 1. Strong count. This is a count of the number of shared pointers that - hold a reference to this object. When the strong counts goes to zero, - if the weak count is zero, the destructor is run. If the weak count is - non-zero when the strong count goes to zero then the partialDestructor - is run. - - 2. Weak count. This is a count of the number of weak pointer that hold - a reference to this object. When the weak count goes to zero and the - strong count is also zero, then the destructor is run. - - 3. Partial destroy started bit. This bit is set if the - `partialDestructor` function has been started (or is about to be - started). This is used to prevent the destructor from running - concurrently with the partial destructor. This can easily happen when - the last strong pointer release its reference in one thread and starts - the partialDestructor, while in another thread the last weak pointer - goes out of scope and starts the destructor while the partialDestructor - is still running. Both a start and finished bit is needed to handle a - corner-case where the last strong pointer goes out of scope, then then - last `weakPointer` goes out of scope, but this happens before the - `partialDestructor` bit is set. It would be possible to use a single - bit if it could also be set atomically when the strong count goes to - zero and the weak count is non-zero, but that would add complexity (and - likely slow down common cases as well). - - 4. Partial destroy finished bit. This bit is set when the - `partialDestructor` has finished running. See (3) above for more - information. - - */ + /** + * `refCounts` consists of four fields that are treated atomically: + * + * 1. Strong count. This is a count of the number of shared pointers that + * hold a reference to this object. When the strong counts goes to zero, + * if the weak count is zero, the destructor is run. If the weak count is + * non-zero when the strong count goes to zero then the partialDestructor + * is run. + * + * 2. Weak count. This is a count of the number of weak pointer that hold + * a reference to this object. When the weak count goes to zero and the + * strong count is also zero, then the destructor is run. + * + * 3. Partial destroy started bit. This bit is set if the + * `partialDestructor` function has been started (or is about to be + * started). This is used to prevent the destructor from running + * concurrently with the partial destructor. This can easily happen when + * the last strong pointer release its reference in one thread and starts + * the partialDestructor, while in another thread the last weak pointer + * goes out of scope and starts the destructor while the partialDestructor + * is still running. Both a start and finished bit is needed to handle a + * corner-case where the last strong pointer goes out of scope, then then + * last `weakPointer` goes out of scope, but this happens before the + * `partialDestructor` bit is set. It would be possible to use a single + * bit if it could also be set atomically when the strong count goes to + * zero and the weak count is non-zero, but that would add complexity (and + * likely slow down common cases as well). + * + * 4. Partial destroy finished bit. This bit is set when the + * `partialDestructor` has finished running. See (3) above for more + * information. + */ mutable std::atomic refCounts_{kStrongDelta}; - /** Amount to change the strong count when adding or releasing a reference - - Note: The strong count is stored in the low `StrongCountNumBits` bits - of refCounts - */ + /** + * Amount to change the strong count when adding or releasing a reference + * + * Note: The strong count is stored in the low `StrongCountNumBits` bits + * of refCounts + */ static constexpr FieldType kStrongDelta = 1; - /** Amount to change the weak count when adding or releasing a reference - - Note: The weak count is stored in the high `WeakCountNumBits` bits of - refCounts - */ + /** + * Amount to change the weak count when adding or releasing a reference + * + * Note: The weak count is stored in the high `WeakCountNumBits` bits of + * refCounts + */ static constexpr FieldType kWeakDelta = (kOne << kStrongCountNumBits); - /** Flag that is set when the partialDestroy function has started running - (or is about to start running). - - See description of the `refCounts` field for a fuller description of - this field. - */ + /** + * Flag that is set when the partialDestroy function has started running + * (or is about to start running). + * + * See description of the `refCounts` field for a fuller description of + * this field. + */ static constexpr FieldType kPartialDestroyStartedMask = (kOne << (kFieldTypeBits - 1)); - /** Flag that is set when the partialDestroy function has finished running - - See description of the `refCounts` field for a fuller description of - this field. - */ + /** + * Flag that is set when the partialDestroy function has finished running + * + * See description of the `refCounts` field for a fuller description of + * this field. + */ static constexpr FieldType kPartialDestroyFinishedMask = (kOne << (kFieldTypeBits - 2)); - /** Mask that will zero out all the `count` bits and leave the tag bits - unchanged. - */ + /** + * Mask that will zero out all the `count` bits and leave the tag bits + * unchanged. + */ static constexpr FieldType kTagMask = kPartialDestroyStartedMask | kPartialDestroyFinishedMask; - /** Mask that will zero out the `tag` bits and leave the count bits - unchanged. - */ + /** + * Mask that will zero out the `tag` bits and leave the count bits + * unchanged. + */ static constexpr FieldType kValueMask = ~kTagMask; - /** Mask that will zero out everything except the strong count. + /** + * Mask that will zero out everything except the strong count. */ static constexpr FieldType kStrongMask = ((kOne << kStrongCountNumBits) - 1) & kValueMask; - /** Mask that will zero out everything except the weak count. + /** + * Mask that will zero out everything except the weak count. */ static constexpr FieldType kWeakMask = (((kOne << kWeakCountNumBits) - 1) << kStrongCountNumBits) & kValueMask; - /** Unpack the count and tag fields from the packed atomic integer form. */ + /** + * Unpack the count and tag fields from the packed atomic integer form. + */ struct RefCountPair { CountType strong; CountType weak; - /** The `partialDestroyStartedBit` is set to on when the partial - destroy function is started. It is not a boolean; it is a uint32 - with all bits zero with the possible exception of the - `partialDestroyStartedMask` bit. This is done so it can be directly - masked into the `combinedValue`. + /** + * The `partialDestroyStartedBit` is set to on when the partial + * destroy function is started. It is not a boolean; it is a uint32 + * with all bits zero with the possible exception of the + * `partialDestroyStartedMask` bit. This is done so it can be directly + * masked into the `combinedValue`. */ FieldType partialDestroyStartedBit{0}; - /** The `partialDestroyFinishedBit` is set to on when the partial - destroy function has finished. + /** + * The `partialDestroyFinishedBit` is set to on when the partial + * destroy function has finished. */ FieldType partialDestroyFinishedBit{0}; RefCountPair(FieldType v) noexcept; RefCountPair(CountType s, CountType w) noexcept; - /** Convert back to the packed integer form. */ + /** + * Convert back to the packed integer form. + */ [[nodiscard]] FieldType combinedValue() const noexcept; @@ -215,9 +232,10 @@ private: static_cast((kOne << kStrongCountNumBits) - 1); static constexpr CountType kMaxWeakValue = static_cast((kOne << kWeakCountNumBits) - 1); - /** Put an extra margin to detect when running up against limits. - This is only used in debug code, and is useful if we reduce the - number of bits in the strong and weak counts (to 16 and 14 bits). + /** + * Put an extra margin to detect when running up against limits. + * This is only used in debug code, and is useful if we reduce the + * number of bits in the strong and weak counts (to 16 and 14 bits). */ static constexpr CountType kCheckStrongMaxValue = kMaxStrongValue - 32; static constexpr CountType kCheckWeakMaxValue = kMaxWeakValue - 32; diff --git a/include/xrpl/basics/LocalValue.h b/include/xrpl/basics/LocalValue.h index 1c2a657a18..c5e544a343 100644 --- a/include/xrpl/basics/LocalValue.h +++ b/include/xrpl/basics/LocalValue.h @@ -70,11 +70,15 @@ public: { } - /** Stores instance of T specific to the calling coroutine or thread. */ + /** + * Stores instance of T specific to the calling coroutine or thread. + */ T& operator*(); - /** Stores instance of T specific to the calling coroutine or thread. */ + /** + * Stores instance of T specific to the calling coroutine or thread. + */ T* operator->() { diff --git a/include/xrpl/basics/Log.h b/include/xrpl/basics/Log.h index 4e3437fe71..945dc1b4ec 100644 --- a/include/xrpl/basics/Log.h +++ b/include/xrpl/basics/Log.h @@ -16,7 +16,9 @@ namespace xrpl { -/** Manages partitions for logging. */ +/** + * Manages partitions for logging. + */ class Logs { private: @@ -40,69 +42,81 @@ private: writeAlways(beast::Severity level, std::string const& text) override; }; - /** Manages a system file containing logged output. - The system file remains open during program execution. Interfaces - are provided for interoperating with standard log management - tools like logrotate(8): - http://linuxcommand.org/man_pages/logrotate8.html - @note None of the listed interfaces are thread-safe. - */ + /** + * Manages a system file containing logged output. + * The system file remains open during program execution. Interfaces + * are provided for interoperating with standard log management + * tools like logrotate(8): + * http://linuxcommand.org/man_pages/logrotate8.html + * @note None of the listed interfaces are thread-safe. + */ class File { public: - /** Construct with no associated system file. - A system file may be associated later with @ref open. - @see open - */ + /** + * Construct with no associated system file. + * A system file may be associated later with @ref open. + * @see open + */ File(); - /** Destroy the object. - If a system file is associated, it will be flushed and closed. - */ + /** + * Destroy the object. + * If a system file is associated, it will be flushed and closed. + */ ~File() = default; - /** Determine if a system file is associated with the log. - @return `true` if a system file is associated and opened for - writing. - */ + /** + * Determine if a system file is associated with the log. + * @return `true` if a system file is associated and opened for + * writing. + */ [[nodiscard]] bool isOpen() const noexcept; - /** Associate a system file with the log. - If the file does not exist an attempt is made to create it - and open it for writing. If the file already exists an attempt is - made to open it for appending. - If a system file is already associated with the log, it is closed - first. - @return `true` if the file was opened. - */ + /** + * Associate a system file with the log. + * If the file does not exist an attempt is made to create it + * and open it for writing. If the file already exists an attempt is + * made to open it for appending. + * If a system file is already associated with the log, it is closed + * first. + * @return `true` if the file was opened. + */ bool open(boost::filesystem::path const& path); - /** Close and re-open the system file associated with the log - This assists in interoperating with external log management tools. - @return `true` if the file was opened. - */ + /** + * Close and re-open the system file associated with the log + * This assists in interoperating with external log management tools. + * @return `true` if the file was opened. + */ bool closeAndReopen(); - /** Close the system file if it is open. */ + /** + * Close the system file if it is open. + */ void close(); - /** write to the log file. - Does nothing if there is no associated system file. - */ + /** + * write to the log file. + * Does nothing if there is no associated system file. + */ void write(char const* text); - /** write to the log file and append an end of line marker. - Does nothing if there is no associated system file. - */ + /** + * write to the log file and append an end of line marker. + * Does nothing if there is no associated system file. + */ void writeln(char const* text); - /** Write to the log file using std::string. */ + /** + * Write to the log file using std::string. + */ /** @{ */ void write(std::string const& str) @@ -223,19 +237,21 @@ private: //------------------------------------------------------------------------------ // Debug logging: -/** Set the sink for the debug journal. - - @param sink unique_ptr to new debug Sink. - @return unique_ptr to the previous Sink. nullptr if there was no Sink. -*/ +/** + * Set the sink for the debug journal. + * + * @param sink unique_ptr to new debug Sink. + * @return unique_ptr to the previous Sink. nullptr if there was no Sink. + */ std::unique_ptr setDebugLogSink(std::unique_ptr sink); -/** Returns a debug journal. - The journal may drain to a null sink, so its output - may never be seen. Never use it for critical - information. -*/ +/** + * Returns a debug journal. + * The journal may drain to a null sink, so its output + * may never be seen. Never use it for critical + * information. + */ beast::Journal debugLog(); diff --git a/include/xrpl/basics/MathUtilities.h b/include/xrpl/basics/MathUtilities.h index 4552b335e1..78f5c76988 100644 --- a/include/xrpl/basics/MathUtilities.h +++ b/include/xrpl/basics/MathUtilities.h @@ -6,7 +6,8 @@ namespace xrpl { -/** Calculate one number divided by another number in percentage. +/** + * Calculate one number divided by another number in percentage. * The result is rounded up to the next integer, and capped in the range [0,100] * E.g. calculatePercent(1, 100) = 1 because 1/100 = 0.010000 * calculatePercent(1, 99) = 2 because 1/99 = 0.010101 @@ -19,7 +20,7 @@ namespace xrpl { * @return the percentage, in [0, 100] * * @note total cannot be zero. - * */ + */ constexpr std::size_t calculatePercent(std::size_t count, std::size_t total) { diff --git a/include/xrpl/basics/Number.h b/include/xrpl/basics/Number.h index 073da12f89..0026e7f006 100644 --- a/include/xrpl/basics/Number.h +++ b/include/xrpl/basics/Number.h @@ -14,7 +14,6 @@ #include #include #include -#include #include namespace xrpl { @@ -48,46 +47,54 @@ isPowerOfTen(T value) namespace detail { -/** Builds a table of the powers of 10 +/** + * Builds a table of the powers of 10 * * This function is marked consteval, so it can only be run in * a constexpr context. This assures that it is and can only be run at * compile time. Doing it at runtime would be pretty wasteful and * inefficient. */ -constexpr std::size_t kInt64Digits = 20; -consteval std::array +constexpr std::size_t kUint64Digits = 20; +[[maybe_unused]] constexpr std::size_t kUint128Digits = 39; + +template +consteval std::array buildPowersOfTen() { - std::array result{}; + std::array result{}; - std::uint64_t power = 1; + T power = 1; std::size_t exponent = 0; // end the loop early so it doesn't overflow; for (; exponent < result.size() - 1; ++exponent, power *= 10) { result[exponent] = power; - if (power > std::numeric_limits::max() / 10) + if (power > std::numeric_limits::max() / 10) throw std::logic_error("Power of 10 table is too big"); } result[exponent] = power; - if (power < std::numeric_limits::max() / 10) - throw std::logic_error("Power of 10 table is not big enough for the uint64_t type"); + if (power < std::numeric_limits::max() / 10) + throw std::logic_error("Power of 10 table is not big enough for the given type"); return result; } } // namespace detail -constexpr std::array kPowerOfTen = detail::buildPowersOfTen(); +template +constexpr std::array kPowerOfTenImpl = detail::buildPowersOfTen(); + +constexpr auto kPowerOfTen = kPowerOfTenImpl; static_assert(kPowerOfTen[0] == 1); static_assert(kPowerOfTen[1] == 10); static_assert(kPowerOfTen[10] == 10'000'000'000); static_assert( - isPowerOfTen(kPowerOfTen.back()) && *logTen(kPowerOfTen.back()) == detail::kInt64Digits - 1); + isPowerOfTen(kPowerOfTen.back()) && *logTen(kPowerOfTen.back()) == detail::kUint64Digits - 1); -/** MantissaRange defines a range for the mantissa of a normalized Number. +/** + * MantissaRange defines a range for the mantissa of a normalized Number. * * The mantissa is in the range [min, max], where * * min is a power of 10, and @@ -124,17 +131,37 @@ struct MantissaRange final { using rep = std::uint64_t; + // NOLINTBEGIN(readability-enum-initial-value) + // The values don't matter, except for Large enum class MantissaScale { + // Small can be removed when either featureSingleAssetVault or featureLendingProtocol are + // retired Small, // LargeLegacy can be removed when fixCleanup3_2_0 is retired LargeLegacy, - Large, + // Large320 can be removed when fixCleanup3_3_0 is retired + Large320, + // If Large330 is ever the only remaining "Large*" entry, it can be renamed to just "Large". + Large330, + // Large is a de-facto alias for "the latest", and is only here for backward compatibility + // in the extremely unlikely case that a downstream project made use of it. Note that + // because the behavior changed, this may still be a breaking change. + Large = Large330, }; + // NOLINTEND(readability-enum-initial-value) - // This entire enum can be removed when fixCleanup3_2_0 is retired - enum class CuspRoundingFix : bool { - Disabled = false, - Enabled = true, + // This entire enum can be removed when the last relevant amendment is retired + enum class CuspRoundingFix : std::uint8_t { + // Disabled can be removed when fixCleanup3_2_0 is retired + Disabled = 0, + // Enabled320 can be removed when fixCleanup3_3_0 is retired + Enabled320 = 1, + // If we ever get to the point that there's only one entry, remove the entire enum + Enabled330 = 2, + // Enabled is a de-facto alias for "the latest", and is only here for backward compatibility + // in the extremely unlikely case that a downstream project made use of it. Note that + // because the behavior changed, this may still be a breaking change. + Enabled = Enabled330, }; explicit constexpr MantissaRange(MantissaScale sc) : scale(sc) @@ -145,13 +172,27 @@ struct MantissaRange final int const log{getExponent(scale)}; rep const min{getMin(scale, log)}; rep const max{(min * 10) - 1}; - CuspRoundingFix const cuspRoundingFixEnabled{isCuspFixEnabled(scale)}; - - static MantissaRange const& - getMantissaRange(MantissaScale scale); + CuspRoundingFix const cuspRoundingFix{isCuspFixEnabled(scale)}; static std::set const& - getAllScales(); + getAllScales() + { + static std::set const kScales = { + MantissaRange::MantissaScale::Small, + MantissaRange::MantissaScale::LargeLegacy, + MantissaRange::MantissaScale::Large320, + MantissaRange::MantissaScale::Large330, + }; + return kScales; + } + + class Access + { + static constexpr MantissaRange const& + mantissaRange(MantissaScale scale); + + friend Number; + }; private: static constexpr int @@ -162,7 +203,8 @@ private: case MantissaScale::Small: return 15; case MantissaScale::LargeLegacy: - case MantissaScale::Large: + case MantissaScale::Large320: + case MantissaScale::Large330: return 18; // LCOV_EXCL_START default: @@ -191,24 +233,24 @@ private: case MantissaScale::Small: case MantissaScale::LargeLegacy: return CuspRoundingFix::Disabled; - case MantissaScale::Large: - return CuspRoundingFix::Enabled; + case MantissaScale::Large320: + return CuspRoundingFix::Enabled320; + case MantissaScale::Large330: + return CuspRoundingFix::Enabled330; default: // If called in a constexpr context, this throw assures that the build fails if an // invalid scale is used. throw std::runtime_error("Unknown mantissa scale"); // LCOV_EXCL_LINE } } - - static std::unordered_map const& - getRanges(); }; // Like std::integral, but only 64-bit integral types. template concept Integral64 = std::is_same_v || std::is_same_v; -/** Number is a floating point type that can represent a wide range of values. +/** + * Number is a floating point type that can represent a wide range of values. * * It can represent all values that can be represented by an STAmount - * regardless of asset type - XRPAmount, MPTAmount, and IOUAmount, with at least @@ -304,7 +346,6 @@ concept Integral64 = std::is_same_v || std::is_same_v kRange; + class Guard; + void normalize(MantissaRange const& range); - /** Normalize Number components to an arbitrary range. + // Guard has the fields that we need, as well as MantissaRange, so if we have a guard, use that + void + normalize(Guard const& guard); + + /** + * Normalize Number components to an arbitrary range. * * min/maxMantissa are parameters because this function is used by both * normalize(), which reads from kRange, and by normalizeToRange, @@ -571,7 +624,7 @@ private: int& exponent, internalrep const& minMantissa, internalrep const& maxMantissa, - MantissaRange::CuspRoundingFix cuspRoundingFixEnabled); + MantissaRange::CuspRoundingFix cuspRoundingFix); template friend void @@ -581,7 +634,7 @@ private: int& exponent, MantissaRange::rep const& minMantissa, MantissaRange::rep const& maxMantissa, - MantissaRange::CuspRoundingFix cuspRoundingFixEnabled, + MantissaRange::CuspRoundingFix cuspRoundingFix, bool dropped); [[nodiscard]] bool @@ -599,8 +652,6 @@ private: // UB, and can vary across compilers. static internalrep externalToInternal(rep mantissa); - - class Guard; }; constexpr Number::Number(bool negative, internalrep mantissa, int exponent, Unchecked) noexcept @@ -635,7 +686,8 @@ inline Number::Number(rep mantissa) : Number{mantissa, 0} { } -/** Returns the mantissa of the external view of the Number. +/** + * Returns the mantissa of the external view of the Number. * * Please see the "---- External Interface ----" section of the class * documentation for an explanation of why the internal value may be modified. @@ -656,7 +708,8 @@ Number::mantissa() const noexcept return sign * static_cast(m); } -/** Returns the exponent of the external view of the Number. +/** + * Returns the exponent of the external view of the Number. * * Please see the "---- External Interface ----" section of the class * documentation for an explanation of why the internal value may be modified. @@ -862,21 +915,11 @@ squelch(Number const& x, Number const& limit) noexcept return x; } -inline std::string -to_string(MantissaRange::MantissaScale const& scale) -{ - switch (scale) - { - case MantissaRange::MantissaScale::Small: - return "small"; - case MantissaRange::MantissaScale::LargeLegacy: - return "largeLegacy"; - case MantissaRange::MantissaScale::Large: - return "large"; - default: - throw std::runtime_error("Bad scale"); - } -} +std::string +to_string(MantissaRange::MantissaScale const& scale); + +std::string +to_string(Number::RoundingMode const& round); class SaveNumberRoundMode { @@ -915,10 +958,10 @@ public: operator=(NumberRoundModeGuard const&) = delete; }; -/** Sets the new scale and restores the old scale when it leaves scope. +/** + * Sets the new scale and restores the old scale when it leaves scope. * * If you think you need to use this class outside of unit tests, no you don't. - * */ class NumberMantissaScaleGuard { diff --git a/include/xrpl/basics/RangeSet.h b/include/xrpl/basics/RangeSet.h index 2ed543b376..3de882979e 100644 --- a/include/xrpl/basics/RangeSet.h +++ b/include/xrpl/basics/RangeSet.h @@ -13,23 +13,25 @@ namespace xrpl { -/** A closed interval over the domain T. - - For an instance ClosedInterval c, this represents the closed interval - (c.first(), c.last()). A single element interval has c.first() == c.last(). - - This is simply a type-alias for boost interval container library interval - set, so users should consult that documentation for available supporting - member and free functions. -*/ +/** + * A closed interval over the domain T. + * + * For an instance ClosedInterval c, this represents the closed interval + * (c.first(), c.last()). A single element interval has c.first() == c.last(). + * + * This is simply a type-alias for boost interval container library interval + * set, so users should consult that documentation for available supporting + * member and free functions. + */ template using ClosedInterval = boost::icl::closed_interval; -/** Create a closed range interval - - Helper function to create a closed range interval without having to qualify - the template argument. -*/ +/** + * Create a closed range interval + * + * Helper function to create a closed range interval without having to qualify + * the template argument. + */ template ClosedInterval range(T low, T high) @@ -37,28 +39,30 @@ range(T low, T high) return ClosedInterval(low, high); } -/** A set of closed intervals over the domain T. - - Represents a set of values of the domain T using the minimum number - of disjoint ClosedInterval. This is useful to represent ranges of - T where a few instances are missing, e.g. the set 1-5,8-9,11-14. - - This is simply a type-alias for boost interval container library interval - set, so users should consult that documentation for available supporting - member and free functions. -*/ +/** + * A set of closed intervals over the domain T. + * + * Represents a set of values of the domain T using the minimum number + * of disjoint ClosedInterval. This is useful to represent ranges of + * T where a few instances are missing, e.g. the set 1-5,8-9,11-14. + * + * This is simply a type-alias for boost interval container library interval + * set, so users should consult that documentation for available supporting + * member and free functions. + */ template using RangeSet = boost::icl::interval_set>; -/** Convert a ClosedInterval to a styled string - - The styled string is - "c.first()-c.last()" if c.first() != c.last() - "c.first()" if c.first() == c.last() - - @param ci The closed interval to convert - @return The style string -*/ +/** + * Convert a ClosedInterval to a styled string + * + * The styled string is + * "c.first()-c.last()" if c.first() != c.last() + * "c.first()" if c.first() == c.last() + * + * @param ci The closed interval to convert + * @return The style string + */ template std::string to_string(ClosedInterval const& ci) @@ -68,14 +72,15 @@ to_string(ClosedInterval const& ci) return std::to_string(ci.first()) + "-" + std::to_string(ci.last()); } -/** Convert the given RangeSet to a styled string. - - The styled string representation is the set of disjoint intervals joined - by commas. The string "empty" is returned if the set is empty. - - @param rs The rangeset to convert - @return The styled string -*/ +/** + * Convert the given RangeSet to a styled string. + * + * The styled string representation is the set of disjoint intervals joined + * by commas. The string "empty" is returned if the set is empty. + * + * @param rs The rangeset to convert + * @return The styled string + */ template std::string to_string(RangeSet const& rs) @@ -91,15 +96,16 @@ to_string(RangeSet const& rs) return s; } -/** Convert the given styled string to a RangeSet. - - The styled string representation is the set - of disjoint intervals joined by commas. - - @param rs The set to be populated - @param s The styled string to convert - @return True on successfully converting styled string -*/ +/** + * Convert the given styled string to a RangeSet. + * + * The styled string representation is the set + * of disjoint intervals joined by commas. + * + * @param rs The set to be populated + * @param s The styled string to convert + * @return True on successfully converting styled string + */ template [[nodiscard]] bool fromString(RangeSet& rs, std::string const& s) @@ -161,14 +167,15 @@ fromString(RangeSet& rs, std::string const& s) return result; } -/** Find the largest value not in the set that is less than a given value. - - @param rs The set of interest - @param t The value that must be larger than the result - @param minVal (Default is 0) The smallest allowed value - @return The largest v such that minV <= v < t and !contains(rs, v) or - std::nullopt if no such v exists. -*/ +/** + * Find the largest value not in the set that is less than a given value. + * + * @param rs The set of interest + * @param t The value that must be larger than the result + * @param minVal (Default is 0) The smallest allowed value + * @return The largest v such that minV <= v < t and !contains(rs, v) or + * std::nullopt if no such v exists. + */ template std::optional prevMissing(RangeSet const& rs, T t, T minVal = 0) diff --git a/include/xrpl/basics/Resolver.h b/include/xrpl/basics/Resolver.h index d48958b76d..239eb9630e 100644 --- a/include/xrpl/basics/Resolver.h +++ b/include/xrpl/basics/Resolver.h @@ -15,22 +15,29 @@ public: virtual ~Resolver() = 0; - /** Issue an asynchronous stop request. */ + /** + * Issue an asynchronous stop request. + */ virtual void stopAsync() = 0; - /** Issue a synchronous stop request. */ + /** + * Issue a synchronous stop request. + */ virtual void stop() = 0; - /** Issue a synchronous start request. */ + /** + * Issue a synchronous start request. + */ virtual void start() = 0; - /** resolve all hostnames on the list - @param names the names to be resolved - @param handler the handler to call - */ + /** + * resolve all hostnames on the list + * @param names the names to be resolved + * @param handler the handler to call + */ /** @{ */ template void diff --git a/include/xrpl/basics/SharedWeakCachePointer.h b/include/xrpl/basics/SharedWeakCachePointer.h index a143647a1e..1b78af2fae 100644 --- a/include/xrpl/basics/SharedWeakCachePointer.h +++ b/include/xrpl/basics/SharedWeakCachePointer.h @@ -7,13 +7,14 @@ namespace xrpl { -/** A combination of a std::shared_ptr and a std::weak_pointer. - - -This class is a wrapper to a `std::variant` -This class is useful for storing intrusive pointers in tagged caches using less -memory than storing both pointers directly. -*/ +/** + * A combination of a std::shared_ptr and a std::weak_pointer. + * + * + * This class is a wrapper to a `std::variant` + * This class is useful for storing intrusive pointers in tagged caches using less + * memory than storing both pointers directly. + */ template class SharedWeakCachePointer @@ -48,65 +49,79 @@ public: ~SharedWeakCachePointer(); - /** Return a strong pointer if this is already a strong pointer (i.e. don't - lock the weak pointer. Use the `lock` method if that's what's needed) + /** + * Return a strong pointer if this is already a strong pointer (i.e. don't + * lock the weak pointer. Use the `lock` method if that's what's needed) */ [[nodiscard]] std::shared_ptr const& getStrong() const; - /** Return true if this is a strong pointer and the strong pointer is - seated. + /** + * Return true if this is a strong pointer and the strong pointer is + * seated. */ explicit operator bool() const noexcept; - /** Set the pointer to null, decrement the appropriate ref count, and run - the appropriate release action. + /** + * Set the pointer to null, decrement the appropriate ref count, and run + * the appropriate release action. */ void reset(); - /** If this is a strong pointer, return the raw pointer. Otherwise return - null. + /** + * If this is a strong pointer, return the raw pointer. Otherwise return + * null. */ [[nodiscard]] T* get() const; - /** If this is a strong pointer, return the strong count. Otherwise return 0 + /** + * If this is a strong pointer, return the strong count. Otherwise return 0 */ [[nodiscard]] std::size_t useCount() const; - /** Return true if there is a non-zero strong count. */ + /** + * Return true if there is a non-zero strong count. + */ [[nodiscard]] bool expired() const; - /** If this is a strong pointer, return the strong pointer. Otherwise - attempt to lock the weak pointer. + /** + * If this is a strong pointer, return the strong pointer. Otherwise + * attempt to lock the weak pointer. */ [[nodiscard]] std::shared_ptr lock() const; - /** Return true is this represents a strong pointer. */ + /** + * Return true is this represents a strong pointer. + */ [[nodiscard]] bool isStrong() const; - /** Return true is this represents a weak pointer. */ + /** + * Return true is this represents a weak pointer. + */ [[nodiscard]] bool isWeak() const; - /** If this is a weak pointer, attempt to convert it to a strong pointer. - - @return true if successfully converted to a strong pointer (or was - already a strong pointer). Otherwise false. - */ + /** + * If this is a weak pointer, attempt to convert it to a strong pointer. + * + * @return true if successfully converted to a strong pointer (or was + * already a strong pointer). Otherwise false. + */ bool convertToStrong(); - /** If this is a strong pointer, attempt to convert it to a weak pointer. - - @return false if the pointer is null. Otherwise return true. - */ + /** + * If this is a strong pointer, attempt to convert it to a weak pointer. + * + * @return false if the pointer is null. Otherwise return true. + */ bool convertToWeak(); diff --git a/include/xrpl/basics/SlabAllocator.h b/include/xrpl/basics/SlabAllocator.h index 8e741991f6..7b6e88e8bc 100644 --- a/include/xrpl/basics/SlabAllocator.h +++ b/include/xrpl/basics/SlabAllocator.h @@ -33,7 +33,9 @@ class SlabAllocator static_assert(alignof(Type) == 8 || alignof(Type) == 4); - /** A block of memory that is owned by a slab allocator */ + /** + * A block of memory that is owned by a slab allocator + */ struct SlabBlock { // A mutex to protect the freelist for this block: @@ -80,7 +82,9 @@ class SlabAllocator SlabBlock& operator=(SlabBlock&& other) = delete; - /** Determines whether the given pointer belongs to this allocator */ + /** + * Determines whether the given pointer belongs to this allocator + */ bool own(std::uint8_t const* pIn) const noexcept { @@ -107,14 +111,15 @@ class SlabAllocator return ret; } - /** Return an item to this allocator's freelist. - - @param ptr The pointer to the chunk of memory being deallocated. - - @note This is a dangerous, private interface; the item being - returned should belong to this allocator. Debug builds - will check and assert if this is not the case. Release - builds will not. + /** + * Return an item to this allocator's freelist. + * + * @param ptr The pointer to the chunk of memory being deallocated. + * + * @note This is a dangerous, private interface; the item being + * returned should belong to this allocator. Debug builds + * will check and assert if this is not the case. Release + * builds will not. */ void deallocate(std::uint8_t* ptr) noexcept @@ -145,13 +150,14 @@ private: std::size_t const slabSize_; public: - /** Constructs a slab allocator able to allocate objects of a fixed size - - @param count the number of items the slab allocator can allocate; note - that a count of 0 is valid and means that the allocator - is, effectively, disabled. This can be very useful in some - contexts (e.g. when minimal memory usage is needed) and - allows for graceful failure. + /** + * Constructs a slab allocator able to allocate objects of a fixed size + * + * @param count the number of items the slab allocator can allocate; note + * that a count of 0 is valid and means that the allocator + * is, effectively, disabled. This can be very useful in some + * contexts (e.g. when minimal memory usage is needed) and + * allows for graceful failure. */ constexpr explicit SlabAllocator( std::size_t extra, @@ -179,17 +185,20 @@ public: // shutdown process up could make this possible. ~SlabAllocator() = default; - /** Returns the size of the memory block this allocator returns. */ + /** + * Returns the size of the memory block this allocator returns. + */ [[nodiscard]] constexpr std::size_t size() const noexcept { return itemSize_; } - /** Returns a suitably aligned pointer, if one is available. - - @return a pointer to a block of memory from the allocator, or - nullptr if the allocator can't satisfy this request. + /** + * Returns a suitably aligned pointer, if one is available. + * + * @return a pointer to a block of memory from the allocator, or + * nullptr if the allocator can't satisfy this request. */ std::uint8_t* allocate() noexcept @@ -250,12 +259,13 @@ public: return slab->allocate(); } - /** Returns the memory block to the allocator. - - @param ptr A pointer to a memory block. - @param size If non-zero, a hint as to the size of the block. - @return true if this memory block belonged to the allocator and has - been released; false otherwise. + /** + * Returns the memory block to the allocator. + * + * @param ptr A pointer to a memory block. + * @param size If non-zero, a hint as to the size of the block. + * @return true if this memory block belonged to the allocator and has + * been released; false otherwise. */ bool deallocate(std::uint8_t* ptr) noexcept @@ -278,7 +288,9 @@ public: } }; -/** A collection of slab allocators of various sizes for a given type. */ +/** + * A collection of slab allocators of various sizes for a given type. + */ template class SlabAllocatorSet { @@ -345,13 +357,14 @@ public: ~SlabAllocatorSet() = default; - /** Returns a suitably aligned pointer, if one is available. - - @param extra The number of extra bytes, above and beyond the size of - the object, that should be returned by the allocator. - - @return a pointer to a block of memory, or nullptr if the allocator - can't satisfy this request. + /** + * Returns a suitably aligned pointer, if one is available. + * + * @param extra The number of extra bytes, above and beyond the size of + * the object, that should be returned by the allocator. + * + * @return a pointer to a block of memory, or nullptr if the allocator + * can't satisfy this request. */ std::uint8_t* allocate(std::size_t extra) noexcept @@ -368,12 +381,13 @@ public: return nullptr; } - /** Returns the memory block to the allocator. - - @param ptr A pointer to a memory block. - - @return true if this memory block belonged to one of the allocators - in this set and has been released; false otherwise. + /** + * Returns the memory block to the allocator. + * + * @param ptr A pointer to a memory block. + * + * @return true if this memory block belonged to one of the allocators + * in this set and has been released; false otherwise. */ bool deallocate(std::uint8_t* ptr) noexcept diff --git a/include/xrpl/basics/Slice.h b/include/xrpl/basics/Slice.h index f87ca063b8..36e7615c3a 100644 --- a/include/xrpl/basics/Slice.h +++ b/include/xrpl/basics/Slice.h @@ -16,12 +16,13 @@ namespace xrpl { -/** An immutable linear range of bytes. - - A fully constructed Slice is guaranteed to be in a valid state. - A Slice is lightweight and copyable, it retains no ownership - of the underlying memory. -*/ +/** + * An immutable linear range of bytes. + * + * A fully constructed Slice is guaranteed to be in a valid state. + * A Slice is lightweight and copyable, it retains no ownership + * of the underlying memory. + */ class Slice { private: @@ -32,30 +33,37 @@ public: using value_type = std::uint8_t; using const_iterator = value_type const*; - /** Default constructed Slice has length 0. */ + /** + * Default constructed Slice has length 0. + */ Slice() noexcept = default; Slice(Slice const&) noexcept = default; Slice& operator=(Slice const&) noexcept = default; - /** Create a slice pointing to existing memory. */ + /** + * Create a slice pointing to existing memory. + */ Slice(void const* data, std::size_t size) noexcept : data_(reinterpret_cast(data)), size_(size) { } - /** Return `true` if the byte range is empty. */ + /** + * Return `true` if the byte range is empty. + */ [[nodiscard]] bool empty() const noexcept { return size_ == 0; } - /** Returns the number of bytes in the storage. - - This may be zero for an empty range. - */ + /** + * Returns the number of bytes in the storage. + * + * This may be zero for an empty range. + */ /** @{ */ [[nodiscard]] std::size_t size() const noexcept @@ -70,17 +78,20 @@ public: } /** @} */ - /** Return a pointer to beginning of the storage. - @note The return type is guaranteed to be a pointer - to a single byte, to facilitate pointer arithmetic. - */ + /** + * Return a pointer to beginning of the storage. + * @note The return type is guaranteed to be a pointer + * to a single byte, to facilitate pointer arithmetic. + */ [[nodiscard]] std::uint8_t const* data() const noexcept { return data_; } - /** Access raw bytes. */ + /** + * Access raw bytes. + */ std::uint8_t operator[](std::size_t i) const noexcept { @@ -88,7 +99,9 @@ public: return data_[i]; } - /** Advance the buffer. */ + /** + * Advance the buffer. + */ /** @{ */ Slice& operator+=(std::size_t n) @@ -108,7 +121,9 @@ public: } /** @} */ - /** Shrinks the slice by moving its start forward by n characters. */ + /** + * Shrinks the slice by moving its start forward by n characters. + */ void removePrefix(std::size_t n) { @@ -116,7 +131,9 @@ public: size_ -= n; } - /** Shrinks the slice by moving its end backward by n characters. */ + /** + * Shrinks the slice by moving its end backward by n characters. + */ void removeSuffix(std::size_t n) { @@ -147,16 +164,17 @@ public: return data_ + size_; } - /** Return a "sub slice" of given length starting at the given position - - Note that the subslice encompasses the range [pos, pos + rCount), - where rCount is the smaller of count and size() - pos. - - @param pos position of the first character - @count requested length - - @returns The requested subslice, if the request is valid. - @throws std::out_of_range if pos > size() + /** + * Return a "sub slice" of given length starting at the given position + * + * Note that the subslice encompasses the range [pos, pos + rCount), + * where rCount is the smaller of count and size() - pos. + * + * @param pos position of the first character + * @count requested length + * + * @return The requested subslice, if the request is valid. + * @throws std::out_of_range if pos > size() */ [[nodiscard]] Slice substr(std::size_t pos, std::size_t count = std::numeric_limits::max()) const diff --git a/include/xrpl/basics/StringUtilities.h b/include/xrpl/basics/StringUtilities.h index 97df43d68f..2b360d2fda 100644 --- a/include/xrpl/basics/StringUtilities.h +++ b/include/xrpl/basics/StringUtilities.h @@ -17,15 +17,16 @@ namespace xrpl { -/** Format arbitrary binary data as an SQLite "blob literal". - - In SQLite, blob literals must be encoded when used in a query. Per - https://sqlite.org/lang_expr.html#literal_values_constants_ they are - encoded as string literals containing hexadecimal data and preceded - by a single 'X' character. - - @param blob An arbitrary blob of binary data - @return The input, encoded as a blob literal. +/** + * Format arbitrary binary data as an SQLite "blob literal". + * + * In SQLite, blob literals must be encoded when used in a query. Per + * https://sqlite.org/lang_expr.html#literal_values_constants_ they are + * encoded as string literals containing hexadecimal data and preceded + * by a single 'X' character. + * + * @param blob An arbitrary blob of binary data + * @return The input, encoded as a blob literal. */ std::string sqlBlobLiteral(Blob const& blob); @@ -130,11 +131,12 @@ trimWhitespace(std::string str); std::optional toUInt64(std::string const& s); -/** Determines if the given string looks like a TOML-file hosting domain. - - Do not use this function to determine if a particular string is a valid - domain, as this function may reject domains that are otherwise valid and - doesn't check whether the TLD is valid. +/** + * Determines if the given string looks like a TOML-file hosting domain. + * + * Do not use this function to determine if a particular string is a valid + * domain, as this function may reject domains that are otherwise valid and + * doesn't check whether the TLD is valid. */ bool isProperlyFormedTomlDomain(std::string_view domain); diff --git a/include/xrpl/basics/TaggedCache.h b/include/xrpl/basics/TaggedCache.h index 16c87cf833..7bb2cb552b 100644 --- a/include/xrpl/basics/TaggedCache.h +++ b/include/xrpl/basics/TaggedCache.h @@ -41,18 +41,19 @@ struct ReplaceDynamically; } // namespace detail -/** Map/cache combination. - This class implements a cache and a map. The cache keeps objects alive - in the map. The map allows multiple code paths that reference objects - with the same tag to get the same actual object. - - So long as data is in the cache, it will stay in memory. - If it stays in memory even after it is ejected from the cache, - the map will track it. - - @note Callers must not modify data objects that are stored in the cache - unless they hold their own lock over all cache operations. -*/ +/** + * Map/cache combination. + * This class implements a cache and a map. The cache keeps objects alive + * in the map. The map allows multiple code paths that reference objects + * with the same tag to get the same actual object. + * + * So long as data is in the cache, it will stay in memory. + * If it stays in memory even after it is ejected from the cache, + * the map will track it. + * + * @note Callers must not modify data objects that are stored in the cache + * unless they hold their own lock over all cache operations. + */ template < class Key, class T, @@ -82,11 +83,15 @@ public: beast::insight::Collector::ptr const& collector = beast::insight::NullCollector::make()); public: - /** Return the clock associated with the cache. */ + /** + * Return the clock associated with the cache. + */ clock_type& clock(); - /** Returns the number of items in the container. */ + /** + * Returns the number of items in the container. + */ std::size_t size() const; @@ -105,9 +110,10 @@ public: void reset(); - /** Refresh the last access time on a key if present. - @return `true` If the key was found. - */ + /** + * Refresh the last access time on a key if present. + * @return `true` If the key was found. + */ template bool touchIfExists(KeyComparable const& key); @@ -130,14 +136,15 @@ private: SharedPointerType const&, SharedPointerType&>; - /** Shared implementation of the canonicalize family. - - `policy` selects how a collision is resolved when `key` already exists: - detail::ReplaceCached, detail::ReplaceClient or - detail::ReplaceDynamically. For ReplaceDynamically `replaceCallback` is - invoked with the existing strong pointer and returns whether to replace - the cached value with `data`; for the tag policies it is unused. - */ + /** + * Shared implementation of the canonicalize family. + * + * `policy` selects how a collision is resolved when `key` already exists: + * detail::ReplaceCached, detail::ReplaceClient or + * detail::ReplaceDynamically. For ReplaceDynamically `replaceCallback` is + * invoked with the existing strong pointer and returns whether to replace + * the cached value with `data`; for the tag policies it is unused. + */ template bool canonicalizeImpl( @@ -147,69 +154,73 @@ private: Callback&& replaceCallback = nullptr); public: - /** Replace aliased objects with originals. - - Due to concurrency it is possible for two separate objects with - the same content and referring to the same unique "thing" to exist. - This routine eliminates the duplicate and performs a replacement - on the callers shared pointer if needed. - - `replaceCallback` is a callable taking the existing strong pointer and - returning whether to replace the cached value with `data` (true) or to - keep the cached value and write it back into `data` (false). Because the - write-back case mutates `data`, `data` must be writable. - - @param key The key corresponding to the object - @param data A shared pointer to the data corresponding to the object. - @param replaceCallback A callable (existing strong pointer -> bool). - - @return `true` if an existing live entry was found and used; `false` if a new entry was - inserted or an expired tracked entry was re-cached. - **/ + /** + * Replace aliased objects with originals. + * + * Due to concurrency it is possible for two separate objects with + * the same content and referring to the same unique "thing" to exist. + * This routine eliminates the duplicate and performs a replacement + * on the callers shared pointer if needed. + * + * `replaceCallback` is a callable taking the existing strong pointer and + * returning whether to replace the cached value with `data` (true) or to + * keep the cached value and write it back into `data` (false). Because the + * write-back case mutates `data`, `data` must be writable. + * + * @param key The key corresponding to the object + * @param data A shared pointer to the data corresponding to the object. + * @param replaceCallback A callable (existing strong pointer -> bool). + * + * @return `true` if an existing live entry was found and used; `false` if a new entry was + * inserted or an expired tracked entry was re-cached. + */ template bool canonicalize(key_type const& key, SharedPointerType& data, Callback&& replaceCallback); - /** Insert/update the canonical entry for `key`, always replacing the - cached value with `data`. - - If an entry already exists for `key`, the cached value is unconditionally - replaced with `data`; otherwise `data` is inserted. `data` is never - written back, so it may be const. - - @param key The key corresponding to the object. - @param data A shared pointer to the data corresponding to the object. - - @return `true` if an existing live entry was found and used; `false` if a new entry was - inserted or an expired tracked entry was re-cached. - **/ + /** + * Insert/update the canonical entry for `key`, always replacing the + * cached value with `data`. + * + * If an entry already exists for `key`, the cached value is unconditionally + * replaced with `data`; otherwise `data` is inserted. `data` is never + * written back, so it may be const. + * + * @param key The key corresponding to the object. + * @param data A shared pointer to the data corresponding to the object. + * + * @return `true` if an existing live entry was found and used; `false` if a new entry was + * inserted or an expired tracked entry was re-cached. + */ bool canonicalizeReplaceCache(key_type const& key, SharedPointerType const& data); - /** Insert the canonical entry for `key`, keeping any existing cached value. - - If an entry already exists for `key`, the cached value is kept and - written back into `data` so the caller ends up with the canonical - object; otherwise `data` is inserted. Because `data` may be overwritten - it must be writable. - - @param key The key corresponding to the object. - @param data A shared pointer to the data corresponding to the object; - updated to the canonical value when one already exists. - - @return `true` if an existing live entry was found and used; `false` if a new entry was - inserted or an expired tracked entry was re-cached. - **/ + /** + * Insert the canonical entry for `key`, keeping any existing cached value. + * + * If an entry already exists for `key`, the cached value is kept and + * written back into `data` so the caller ends up with the canonical + * object; otherwise `data` is inserted. Because `data` may be overwritten + * it must be writable. + * + * @param key The key corresponding to the object. + * @param data A shared pointer to the data corresponding to the object; + * updated to the canonical value when one already exists. + * + * @return `true` if an existing live entry was found and used; `false` if a new entry was + * inserted or an expired tracked entry was re-cached. + */ bool canonicalizeReplaceClient(key_type const& key, SharedPointerType& data); SharedPointerType fetch(key_type const& key); - /** Insert the element into the container. - If the key already exists, nothing happens. - @return `true` If the element was inserted - */ + /** + * Insert the element into the container. + * If the key already exists, nothing happens. + * @return `true` If the element was inserted + */ template auto insert(key_type const& key, T const& value) -> ReturnType @@ -235,15 +246,18 @@ public: getKeys() const; // CachedSLEs functions. - /** Returns the fraction of cache hits. */ + /** + * Returns the fraction of cache hits. + */ double rate() const; - /** Fetch an item from the cache. - If the digest was not found, Handler - will be called with this signature: - SLE::const_pointer(void) - */ + /** + * Fetch an item from the cache. + * If the digest was not found, Handler + * will be called with this signature: + * SLE::const_pointer(void) + */ template SharedPointerType fetch(key_type const& digest, Handler const& h); diff --git a/include/xrpl/basics/ToString.h b/include/xrpl/basics/ToString.h index e9f8f43633..a54db8a8ce 100644 --- a/include/xrpl/basics/ToString.h +++ b/include/xrpl/basics/ToString.h @@ -5,10 +5,11 @@ namespace xrpl { -/** to_string() generalizes std::to_string to handle bools, chars, and strings. - - It's also possible to provide implementation of to_string for a class - which needs a string implementation. +/** + * to_string() generalizes std::to_string to handle bools, chars, and strings. + * + * It's also possible to provide implementation of to_string for a class + * which needs a string implementation. */ template diff --git a/include/xrpl/basics/UptimeClock.h b/include/xrpl/basics/UptimeClock.h index 502aae7c25..b375de4497 100644 --- a/include/xrpl/basics/UptimeClock.h +++ b/include/xrpl/basics/UptimeClock.h @@ -7,12 +7,13 @@ namespace xrpl { -/** Tracks program uptime to seconds precision. - - The timer caches the current time as a performance optimization. - This allows clients to query the current time thousands of times - per second. -*/ +/** + * Tracks program uptime to seconds precision. + * + * The timer caches the current time as a performance optimization. + * This allows clients to query the current time thousands of times + * per second. + */ class UptimeClock { diff --git a/include/xrpl/basics/base_uint.h b/include/xrpl/basics/base_uint.h index 481a7dbd77..96cfa343e3 100644 --- a/include/xrpl/basics/base_uint.h +++ b/include/xrpl/basics/base_uint.h @@ -63,18 +63,19 @@ struct AlwaysFalseT : std::bool_constant } // namespace detail -/** Integers of any length that is a multiple of 32-bits - - @note This class stores its values internally in big-endian - form and that internal representation is part of the - binary protocol of the XRP Ledger and cannot be changed - arbitrarily without causing breakage. - - @tparam Bits The number of bits this integer should have; must - be at least 64 and a multiple of 32. - @tparam Tag An arbitrary type that functions as a tag and allows - the instantiation of "distinct" types that the same - number of bits. +/** + * Integers of any length that is a multiple of 32-bits + * + * @note This class stores its values internally in big-endian + * form and that internal representation is part of the + * binary protocol of the XRP Ledger and cannot be changed + * arbitrarily without causing breakage. + * + * @tparam Bits The number of bits this integer should have; must + * be at least 64 and a multiple of 32. + * @tparam Tag An arbitrary type that functions as a tag and allows + * the instantiation of "distinct" types that the same + * number of bits. */ template class BaseUInt @@ -154,21 +155,23 @@ public: return data() + kBytes; } - /** Value hashing function. - The seed prevents crafted inputs from causing degenerate parent - containers. - */ + /** + * Value hashing function. + * The seed prevents crafted inputs from causing degenerate parent + * containers. + */ using hasher = HardenedHash<>; //-------------------------------------------------------------------------- private: - /** Construct from a raw pointer. - The buffer pointed to by `data` must be at least Bits/8 bytes. - - @note the structure is used to disambiguate this from the std::uint64_t - constructor: something like base_uint(0) is ambiguous. - */ + /** + * Construct from a raw pointer. + * The buffer pointed to by `data` must be at least Bits/8 bytes. + * + * @note the structure is used to disambiguate this from the std::uint64_t + * constructor: something like base_uint(0) is ambiguous. + */ // NIKB TODO Remove the need for this constructor. struct VoidHelper { @@ -503,13 +506,14 @@ public: h(a.data_.data(), sizeof(a.data_)); } - /** Parse a hex string into a base_uint - - The input must be precisely `2 * bytes` hexadecimal characters - long, with one exception: the value '0'. - - @param sv A null-terminated string of hexadecimal characters - @return true if the input was parsed properly; false otherwise. + /** + * Parse a hex string into a base_uint + * + * The input must be precisely `2 * bytes` hexadecimal characters + * long, with one exception: the value '0'. + * + * @param sv A null-terminated string of hexadecimal characters + * @return true if the input was parsed properly; false otherwise. */ [[nodiscard]] constexpr bool parseHex(std::string_view sv) @@ -595,7 +599,7 @@ template [[nodiscard]] constexpr bool operator==(BaseUInt const& lhs, BaseUInt const& rhs) { - return (lhs <=> rhs) == 0; + return (lhs <=> rhs) == 0; // NOLINT(modernize-use-nullptr) } //------------------------------------------------------------------------------ diff --git a/include/xrpl/basics/chrono.h b/include/xrpl/basics/chrono.h index 61246fc699..b855318524 100644 --- a/include/xrpl/basics/chrono.h +++ b/include/xrpl/basics/chrono.h @@ -21,15 +21,16 @@ using days = using weeks = std::chrono::duration>>; -/** Clock for measuring the network time. - - The epoch is January 1, 2000 - - epoch_offset - = date(2000-01-01) - date(1970-0-01) - = days(10957) - = seconds(946684800) -*/ +/** + * Clock for measuring the network time. + * + * The epoch is January 1, 2000 + * + * epoch_offset + * = date(2000-01-01) - date(1970-0-01) + * = days(10957) + * = seconds(946684800) + */ static constexpr std::chrono::seconds kEpochOffset = date::sys_days{date::year{2000} / 1 / 1} - date::sys_days{date::year{1970} / 1 / 1}; @@ -81,16 +82,21 @@ toStringIso(NetClock::time_point tp) return toStringIso(date::sys_time{tp.time_since_epoch() + kEpochOffset}); } -/** A clock for measuring elapsed time. - - The epoch is unspecified. -*/ +/** + * A clock for measuring elapsed time. + * + * The epoch is unspecified. + */ using Stopwatch = beast::AbstractClock; -/** A manual Stopwatch for unit tests. */ +/** + * A manual Stopwatch for unit tests. + */ using TestStopwatch = beast::ManualClock; -/** Returns an instance of a wall clock. */ +/** + * Returns an instance of a wall clock. + */ inline Stopwatch& stopwatch() { diff --git a/include/xrpl/basics/contract.h b/include/xrpl/basics/contract.h index 0e90687de3..6588cb5d1a 100644 --- a/include/xrpl/basics/contract.h +++ b/include/xrpl/basics/contract.h @@ -15,20 +15,23 @@ namespace xrpl { preconditions, postconditions, and invariants. */ -/** Generates and logs a call stack */ +/** + * Generates and logs a call stack + */ void logThrow(std::string const& title); -/** Rethrow the exception currently being handled. - - When called from within a catch block, it will pass - control to the next matching exception handler, if any. - Otherwise, std::terminate will be called. - - ASAN can't handle sudden jumps in control flow very well. This - function is marked as XRPL_NO_SANITIZE_ADDRESS to prevent it from - triggering false positives, since it throws. -*/ +/** + * Rethrow the exception currently being handled. + * + * When called from within a catch block, it will pass + * control to the next matching exception handler, if any. + * Otherwise, std::terminate will be called. + * + * ASAN can't handle sudden jumps in control flow very well. This + * function is marked as XRPL_NO_SANITIZE_ADDRESS to prevent it from + * triggering false positives, since it throws. + */ [[noreturn]] XRPL_NO_SANITIZE_ADDRESS inline void rethrow() { @@ -56,7 +59,9 @@ Throw(Args&&... args) throw std::move(e); } -/** Called when faulty logic causes a broken invariant. */ +/** + * Called when faulty logic causes a broken invariant. + */ [[noreturn]] void logicError(std::string const& how) noexcept; diff --git a/include/xrpl/basics/hardened_hash.h b/include/xrpl/basics/hardened_hash.h index 5a855736b3..6b8277a560 100644 --- a/include/xrpl/basics/hardened_hash.h +++ b/include/xrpl/basics/hardened_hash.h @@ -39,33 +39,33 @@ makeSeedPair() noexcept /** * Seed functor once per construction - - A std compatible hash adapter that resists adversarial inputs. - For this to work, T must implement in its own namespace: - - @code - - template - void - hash_append (Hasher& h, T const& t) noexcept - { - // hash_append each base and member that should - // participate in forming the hash - using beast::hash_append; - hash_append (h, static_cast(t)); - hash_append (h, static_cast(t)); - // ... - hash_append (h, t.member1); - hash_append (h, t.member2); - // ... - } - - @endcode - - Do not use any version of Murmur or CityHash for the Hasher - template parameter (the hashing algorithm). For details - see https://131002.net/siphash/#at -*/ + * + * A std compatible hash adapter that resists adversarial inputs. + * For this to work, T must implement in its own namespace: + * + * @code + * + * template + * void + * hash_append (Hasher& h, T const& t) noexcept + * { + * // hash_append each base and member that should + * // participate in forming the hash + * using beast::hash_append; + * hash_append (h, static_cast(t)); + * hash_append (h, static_cast(t)); + * // ... + * hash_append (h, t.member1); + * hash_append (h, t.member2); + * // ... + * } + * + * @endcode + * + * Do not use any version of Murmur or CityHash for the Hasher + * template parameter (the hashing algorithm). For details + * see https://131002.net/siphash/#at + */ template class HardenedHash diff --git a/include/xrpl/basics/make_SSLContext.h b/include/xrpl/basics/make_SSLContext.h index 45ac637c36..c8ada176f9 100644 --- a/include/xrpl/basics/make_SSLContext.h +++ b/include/xrpl/basics/make_SSLContext.h @@ -7,11 +7,15 @@ namespace xrpl { -/** Create a self-signed SSL context that allows anonymous Diffie Hellman. */ +/** + * Create a self-signed SSL context that allows anonymous Diffie Hellman. + */ std::shared_ptr makeSslContext(std::string const& cipherList); -/** Create an authenticated SSL context using the specified files. */ +/** + * Create an authenticated SSL context using the specified files. + */ std::shared_ptr makeSslContextAuthed( std::string const& keyFile, diff --git a/include/xrpl/basics/mulDiv.h b/include/xrpl/basics/mulDiv.h index 9076da62f2..38fa57294b 100644 --- a/include/xrpl/basics/mulDiv.h +++ b/include/xrpl/basics/mulDiv.h @@ -7,16 +7,16 @@ namespace xrpl { constexpr auto kMuldivMax = std::numeric_limits::max(); -/** Return value*mul/div accurately. - Computes the result of the multiplication and division in - a single step, avoiding overflow and retaining precision. - Throws: - None - Returns: - `std::optional`: - `std::nullopt` if the calculation overflows. Otherwise, `value * mul - / div`. -*/ +/** + * Return value*mul/div accurately. + * + * Computes the result of the multiplication and division in + * a single step, avoiding overflow and retaining precision. + * + * @throws None + * @return `std::nullopt` if the calculation overflows. Otherwise, + * `value * mul / div`. + */ std::optional mulDiv(std::uint64_t value, std::uint64_t mul, std::uint64_t div); diff --git a/include/xrpl/basics/partitioned_unordered_map.h b/include/xrpl/basics/partitioned_unordered_map.h index c2750a5769..e78043e252 100644 --- a/include/xrpl/basics/partitioned_unordered_map.h +++ b/include/xrpl/basics/partitioned_unordered_map.h @@ -138,11 +138,8 @@ public: { } - ConstIterator(Iterator const& orig) + ConstIterator(Iterator const& orig) : map(orig.map), ait(orig.ait), mit(orig.mit) { - map = orig.map; - ait = orig.ait; - mit = orig.mit; } const_reference @@ -231,11 +228,11 @@ private: public: PartitionedUnorderedMap(std::optional partitions = std::nullopt) - { // Set partitions to the number of hardware threads if the parameter // is either empty or set to 0. - partitions_ = - partitions && (*partitions != 0u) ? *partitions : std::thread::hardware_concurrency(); + : partitions_( + partitions && (*partitions != 0u) ? *partitions : std::thread::hardware_concurrency()) + { map_.resize(partitions_); XRPL_ASSERT( partitions_, diff --git a/include/xrpl/basics/random.h b/include/xrpl/basics/random.h index c544e7d0c8..7aeb7d6145 100644 --- a/include/xrpl/basics/random.h +++ b/include/xrpl/basics/random.h @@ -33,16 +33,17 @@ template using is_engine = std::is_invocable_r; } // namespace detail -/** Return the default random engine. - - This engine is guaranteed to be deterministic, but by - default will be randomly seeded. It is NOT cryptographically - secure and MUST NOT be used to generate randomness that - will be used for keys, secure cookies, IVs, padding, etc. - - Each thread gets its own instance of the engine which - will be randomly seeded. -*/ +/** + * Return the default random engine. + * + * This engine is guaranteed to be deterministic, but by + * default will be randomly seeded. It is NOT cryptographically + * secure and MUST NOT be used to generate randomness that + * will be used for keys, secure cookies, IVs, padding, etc. + * + * Each thread gets its own instance of the engine which + * will be randomly seeded. + */ inline beast::xor_shift_engine& defaultPrng() { @@ -70,25 +71,26 @@ defaultPrng() return kEngine; } -/** Return a uniformly distributed random integer. - - @param min The smallest value to return. If not specified - the value defaults to 0. - @param max The largest value to return. If not specified - the value defaults to the largest value that - can be represented. - - The randomness is generated by the specified engine (or - the default engine if one is not specified). The result - is cryptographically secure only when the engine passed - into the function is cryptographically secure. - - @note The range is always a closed interval, so calling - rand_int(-5, 15) can return any integer in the - closed interval [-5, 15]; similarly, calling - rand_int(7) can return any integer in the closed - interval [0, 7]. -*/ +/** + * Return a uniformly distributed random integer. + * + * @param min The smallest value to return. If not specified + * the value defaults to 0. + * @param max The largest value to return. If not specified + * the value defaults to the largest value that + * can be represented. + * + * The randomness is generated by the specified engine (or + * the default engine if one is not specified). The result + * is cryptographically secure only when the engine passed + * into the function is cryptographically secure. + * + * @note The range is always a closed interval, so calling + * rand_int(-5, 15) can return any integer in the + * closed interval [-5, 15]; similarly, calling + * rand_int(7) can return any integer in the closed + * interval [0, 7]. + */ /** @{ */ template Integral @@ -144,7 +146,9 @@ randInt() } /** @} */ -/** Return a random byte */ +/** + * Return a random byte + */ /** @{ */ template Byte @@ -166,7 +170,9 @@ randByte() } /** @} */ -/** Return a random boolean value */ +/** + * Return a random boolean value + */ /** @{ */ template inline bool diff --git a/include/xrpl/basics/scope.h b/include/xrpl/basics/scope.h index e63bb69eb5..5821e1dacc 100644 --- a/include/xrpl/basics/scope.h +++ b/include/xrpl/basics/scope.h @@ -156,41 +156,41 @@ template ScopeSuccess(EF) -> ScopeSuccess; /** - Automatically unlocks and re-locks a unique_lock object. - - This is the reverse of a std::unique_lock object - instead of locking the - mutex for the lifetime of this object, it unlocks it. - - Make sure you don't try to unlock mutexes that aren't actually locked! - - This is essentially a less-versatile boost::reverse_lock. - - e.g. @code - - std::mutex mut; - - for (;;) - { - std::unique_lock myScopedLock{mut}; - // mut is now locked - - ... do some stuff with it locked .. - - while (xyz) - { - ... do some stuff with it locked .. - - scope_unlock unlocker{myScopedLock}; - - // mut is now unlocked for the remainder of this block, - // and re-locked at the end. - - ...do some stuff with it unlocked ... - } // mut gets locked here. - - } // mut gets unlocked here - @endcode -*/ + * Automatically unlocks and re-locks a unique_lock object. + * + * This is the reverse of a std::unique_lock object - instead of locking the + * mutex for the lifetime of this object, it unlocks it. + * + * Make sure you don't try to unlock mutexes that aren't actually locked! + * + * This is essentially a less-versatile boost::reverse_lock. + * + * e.g. @code + * + * std::mutex mut; + * + * for (;;) + * { + * std::unique_lock myScopedLock{mut}; + * // mut is now locked + * + * ... do some stuff with it locked .. + * + * while (xyz) + * { + * ... do some stuff with it locked .. + * + * scope_unlock unlocker{myScopedLock}; + * + * // mut is now unlocked for the remainder of this block, + * // and re-locked at the end. + * + * ...do some stuff with it unlocked ... + * } // mut gets locked here. + * + * } // mut gets unlocked here + * @endcode + */ template class ScopeUnlock diff --git a/include/xrpl/basics/spinlock.h b/include/xrpl/basics/spinlock.h index 2cc00efdef..87611f20ba 100644 --- a/include/xrpl/basics/spinlock.h +++ b/include/xrpl/basics/spinlock.h @@ -15,15 +15,16 @@ namespace xrpl { namespace detail { -/** Inform the processor that we are in a tight spin-wait loop. - - Spinlocks caught in tight loops can result in the processor's pipeline - filling up with comparison operations, resulting in a misprediction at - the time the lock is finally acquired, necessitating pipeline flushing - which is ridiculously expensive and results in very high latency. - - This function instructs the processor to "pause" for some architecture - specific amount of time, to prevent this. +/** + * Inform the processor that we are in a tight spin-wait loop. + * + * Spinlocks caught in tight loops can result in the processor's pipeline + * filling up with comparison operations, resulting in a misprediction at + * the time the lock is finally acquired, necessitating pipeline flushing + * which is ridiculously expensive and results in very high latency. + * + * This function instructs the processor to "pause" for some architecture + * specific amount of time, to prevent this. */ inline void spinPause() noexcept @@ -38,37 +39,39 @@ spinPause() noexcept } // namespace detail /** @{ */ -/** Classes to handle arrays of spinlocks packed into a single atomic integer: - - Packed spinlocks allow for tremendously space-efficient lock-sharding - but they come at a cost. - - First, the implementation is necessarily low-level and uses advanced - features like memory ordering and highly platform-specific tricks to - maximize performance. This imposes a significant and ongoing cost to - developers. - - Second, and perhaps most important, is that the packing of multiple - locks into a single integer which, albeit space-efficient, also has - performance implications stemming from data dependencies, increased - cache-coherency traffic between processors and heavier loads on the - processor's load/store units. - - To be sure, these locks can have advantages but they are definitely - not general purpose locks and should not be thought of or used that - way. The use cases for them are likely few and far between; without - a compelling reason to use them, backed by profiling data, it might - be best to use one of the standard locking primitives instead. Note - that in most common platforms, `std::mutex` is so heavily optimized - that it can, usually, outperform spinlocks. - - @tparam T An unsigned integral type (e.g. std::uint16_t) +/** + * Classes to handle arrays of spinlocks packed into a single atomic integer: + * + * Packed spinlocks allow for tremendously space-efficient lock-sharding + * but they come at a cost. + * + * First, the implementation is necessarily low-level and uses advanced + * features like memory ordering and highly platform-specific tricks to + * maximize performance. This imposes a significant and ongoing cost to + * developers. + * + * Second, and perhaps most important, is that the packing of multiple + * locks into a single integer which, albeit space-efficient, also has + * performance implications stemming from data dependencies, increased + * cache-coherency traffic between processors and heavier loads on the + * processor's load/store units. + * + * To be sure, these locks can have advantages but they are definitely + * not general purpose locks and should not be thought of or used that + * way. The use cases for them are likely few and far between; without + * a compelling reason to use them, backed by profiling data, it might + * be best to use one of the standard locking primitives instead. Note + * that in most common platforms, `std::mutex` is so heavily optimized + * that it can, usually, outperform spinlocks. + * + * @tparam T An unsigned integral type (e.g. std::uint16_t) */ -/** A class that grabs a single packed spinlock from an atomic integer. - - This class meets the requirements of Lockable: - https://en.cppreference.com/w/cpp/named_req/Lockable +/** + * A class that grabs a single packed spinlock from an atomic integer. + * + * This class meets the requirements of Lockable: + * https://en.cppreference.com/w/cpp/named_req/Lockable */ template class PackedSpinlock @@ -91,13 +94,14 @@ public: PackedSpinlock& operator=(PackedSpinlock const&) = delete; - /** A single spinlock packed inside the specified atomic - - @param lock The atomic integer inside which the spinlock is packed. - @param index The index of the spinlock this object acquires. - - @note For performance reasons, you should strive to have `lock` be - on a cacheline by itself. + /** + * A single spinlock packed inside the specified atomic + * + * @param lock The atomic integer inside which the spinlock is packed. + * @param index The index of the spinlock this object acquires. + * + * @note For performance reasons, you should strive to have `lock` be + * on a cacheline by itself. */ PackedSpinlock(std::atomic& lock, int index) : bits_(lock), mask_(static_cast(1) << index) { @@ -133,17 +137,18 @@ public: } }; -/** A spinlock implemented on top of an atomic integer. - - @note Using `packed_spinlock` and `spinlock` against the same underlying - atomic integer can result in `spinlock` not being able to actually - acquire the lock during periods of high contention, because of how - the two locks operate: `spinlock` will spin trying to grab all the - bits at once, whereas any given `packed_spinlock` will only try to - grab one bit at a time. Caveat emptor. - - This class meets the requirements of Lockable: - https://en.cppreference.com/w/cpp/named_req/Lockable +/** + * A spinlock implemented on top of an atomic integer. + * + * @note Using `packed_spinlock` and `spinlock` against the same underlying + * atomic integer can result in `spinlock` not being able to actually + * acquire the lock during periods of high contention, because of how + * the two locks operate: `spinlock` will spin trying to grab all the + * bits at once, whereas any given `packed_spinlock` will only try to + * grab one bit at a time. Caveat emptor. + * + * This class meets the requirements of Lockable: + * https://en.cppreference.com/w/cpp/named_req/Lockable */ template class Spinlock @@ -159,12 +164,13 @@ public: Spinlock& operator=(Spinlock const&) = delete; - /** Grabs the - - @param lock The atomic integer to spin against. - - @note For performance reasons, you should strive to have `lock` be - on a cacheline by itself. + /** + * Grabs the + * + * @param lock The atomic integer to spin against. + * + * @note For performance reasons, you should strive to have `lock` be + * on a cacheline by itself. */ Spinlock(std::atomic& lock) : lock_(lock) { diff --git a/include/xrpl/basics/tagged_integer.h b/include/xrpl/basics/tagged_integer.h index 5a088db863..2edb314a16 100644 --- a/include/xrpl/basics/tagged_integer.h +++ b/include/xrpl/basics/tagged_integer.h @@ -12,17 +12,18 @@ namespace xrpl { -/** A type-safe wrap around standard integral types - - The tag is used to implement type safety, catching mismatched types at - compile time. Multiple instantiations wrapping the same underlying integral - type are distinct types (distinguished by tag) and will not interoperate. A - tagged_integer supports all the usual assignment, arithmetic, comparison and - shifting operations defined for the underlying type - - The tag is not meant as a unit, which would require restricting the set of - allowed arithmetic operations. -*/ +/** + * A type-safe wrap around standard integral types + * + * The tag is used to implement type safety, catching mismatched types at + * compile time. Multiple instantiations wrapping the same underlying integral + * type are distinct types (distinguished by tag) and will not interoperate. A + * tagged_integer supports all the usual assignment, arithmetic, comparison and + * shifting operations defined for the underlying type + * + * The tag is not meant as a unit, which would require restricting the set of + * allowed arithmetic operations. + */ template class TaggedInteger : boost::totally_ordered< TaggedInteger, diff --git a/include/xrpl/beast/asio/io_latency_probe.h b/include/xrpl/beast/asio/io_latency_probe.h index f67ff4a692..d87bdafe45 100644 --- a/include/xrpl/beast/asio/io_latency_probe.h +++ b/include/xrpl/beast/asio/io_latency_probe.h @@ -14,7 +14,9 @@ namespace beast { -/** Measures handler latency on an io_context queue. */ +/** + * Measures handler latency on an io_context queue. + */ template class IOLatencyProbe { @@ -42,7 +44,9 @@ public: cancel(lock, true); } - /** Return the io_context associated with the latency probe. */ + /** + * Return the io_context associated with the latency probe. + */ /** @{ */ boost::asio::io_context& getIoContext() @@ -57,9 +61,10 @@ public: } /** @} */ - /** Cancel all pending i/o. - Any handlers which have already been queued will still be called. - */ + /** + * Cancel all pending i/o. + * Any handlers which have already been queued will still be called. + */ /** @{ */ void cancel() @@ -76,10 +81,11 @@ public: } /** @} */ - /** Measure one sample of i/o latency. - Handler will be called with this signature: - void Handler (Duration d); - */ + /** + * Measure one sample of i/o latency. + * Handler will be called with this signature: + * void Handler (Duration d); + */ template void sampleOne(Handler&& handler) @@ -91,10 +97,11 @@ public: ios_, SampleOp(std::forward(handler), Clock::now(), false, this)); } - /** Initiate continuous i/o latency sampling. - Handler will be called with this signature: - void Handler (std::chrono::milliseconds); - */ + /** + * Initiate continuous i/o latency sampling. + * Handler will be called with this signature: + * void Handler (std::chrono::milliseconds); + */ template void sample(Handler&& handler) diff --git a/include/xrpl/beast/clock/abstract_clock.h b/include/xrpl/beast/clock/abstract_clock.h index 15d785d138..6e23700730 100644 --- a/include/xrpl/beast/clock/abstract_clock.h +++ b/include/xrpl/beast/clock/abstract_clock.h @@ -2,34 +2,35 @@ namespace beast { -/** Abstract interface to a clock. - - This makes now() a member function instead of a static member, so - an instance of the class can be dependency injected, facilitating - unit tests where time may be controlled. - - An abstract_clock inherits all the nested types of the Clock - template parameter. - - Example: - - @code - - struct Implementation - { - using clock_type = abstract_clock ; - clock_type& clock_; - explicit Implementation (clock_type& clock) - : clock_(clock) - { - } - }; - - @endcode - - @tparam Clock A type meeting these requirements: - http://en.cppreference.com/w/cpp/concept/Clock -*/ +/** + * Abstract interface to a clock. + * + * This makes now() a member function instead of a static member, so + * an instance of the class can be dependency injected, facilitating + * unit tests where time may be controlled. + * + * An abstract_clock inherits all the nested types of the Clock + * template parameter. + * + * Example: + * + * @code + * + * struct Implementation + * { + * using clock_type = abstract_clock ; + * clock_type& clock_; + * explicit Implementation (clock_type& clock) + * : clock_(clock) + * { + * } + * }; + * + * @endcode + * + * @tparam Clock A type meeting these requirements: + * http://en.cppreference.com/w/cpp/concept/Clock + */ template class AbstractClock { @@ -46,7 +47,9 @@ public: AbstractClock() = default; AbstractClock(AbstractClock const&) = default; - /** Returns the current time. */ + /** + * Returns the current time. + */ [[nodiscard]] virtual time_point now() const = 0; }; @@ -74,11 +77,12 @@ struct AbstractClockWrapper : public AbstractClock //------------------------------------------------------------------------------ -/** Returns a global instance of an abstract clock. - @tparam Facade A type meeting these requirements: - http://en.cppreference.com/w/cpp/concept/Clock - @tparam Clock The actual concrete clock to use. -*/ +/** + * Returns a global instance of an abstract clock. + * @tparam Facade A type meeting these requirements: + * http://en.cppreference.com/w/cpp/concept/Clock + * @tparam Clock The actual concrete clock to use. + */ template AbstractClock& getAbstractClock() diff --git a/include/xrpl/beast/clock/basic_seconds_clock.h b/include/xrpl/beast/clock/basic_seconds_clock.h index 5a267e9458..dce521d0b8 100644 --- a/include/xrpl/beast/clock/basic_seconds_clock.h +++ b/include/xrpl/beast/clock/basic_seconds_clock.h @@ -4,15 +4,16 @@ namespace beast { -/** A clock whose minimum resolution is one second. - - The purpose of this class is to optimize the performance of the now() - member function call. It uses a dedicated thread that wakes up at least - once per second to sample the requested trivial clock. - - @tparam Clock A type meeting these requirements: - http://en.cppreference.com/w/cpp/concept/Clock -*/ +/** + * A clock whose minimum resolution is one second. + * + * The purpose of this class is to optimize the performance of the now() + * member function call. It uses a dedicated thread that wakes up at least + * once per second to sample the requested trivial clock. + * + * @tparam Clock A type meeting these requirements: + * http://en.cppreference.com/w/cpp/concept/Clock + */ class BasicSecondsClock { public: diff --git a/include/xrpl/beast/clock/manual_clock.h b/include/xrpl/beast/clock/manual_clock.h index 8b3e4e63c6..4dc9553644 100644 --- a/include/xrpl/beast/clock/manual_clock.h +++ b/include/xrpl/beast/clock/manual_clock.h @@ -7,15 +7,16 @@ namespace beast { -/** Manual clock implementation. - - This concrete class implements the @ref abstract_clock interface and - allows the time to be advanced manually, mainly for the purpose of - providing a clock in unit tests. - - @tparam Clock A type meeting these requirements: - http://en.cppreference.com/w/cpp/concept/Clock -*/ +/** + * Manual clock implementation. + * + * This concrete class implements the @ref abstract_clock interface and + * allows the time to be advanced manually, mainly for the purpose of + * providing a clock in unit tests. + * + * @tparam Clock A type meeting these requirements: + * http://en.cppreference.com/w/cpp/concept/Clock + */ template class ManualClock : public AbstractClock { @@ -38,7 +39,9 @@ public: return now_; } - /** Set the current time of the manual clock. */ + /** + * Set the current time of the manual clock. + */ void set(time_point const& when) { @@ -48,7 +51,9 @@ public: now_ = when; } - /** Convenience for setting the time in seconds from epoch. */ + /** + * Convenience for setting the time in seconds from epoch. + */ template void set(Integer secondsFromEpoch) @@ -56,7 +61,9 @@ public: set(time_point(duration(std::chrono::seconds(secondsFromEpoch)))); } - /** Advance the clock by a duration. */ + /** + * Advance the clock by a duration. + */ template void advance(std::chrono::duration const& elapsed) @@ -67,7 +74,9 @@ public: now_ += elapsed; } - /** Convenience for advancing the clock by one second. */ + /** + * Convenience for advancing the clock by one second. + */ ManualClock& operator++() { diff --git a/include/xrpl/beast/container/aged_container_utility.h b/include/xrpl/beast/container/aged_container_utility.h index f43e59b0f7..da3e4e0500 100644 --- a/include/xrpl/beast/container/aged_container_utility.h +++ b/include/xrpl/beast/container/aged_container_utility.h @@ -7,7 +7,9 @@ namespace beast { -/** Expire aged container items past the specified age. */ +/** + * Expire aged container items past the specified age. + */ template std::size_t expire(AgedContainer& c, std::chrono::duration const& age) diff --git a/include/xrpl/beast/container/detail/aged_ordered_container.h b/include/xrpl/beast/container/detail/aged_ordered_container.h index f739f05d2c..5b60ef7e6d 100644 --- a/include/xrpl/beast/container/detail/aged_ordered_container.h +++ b/include/xrpl/beast/container/detail/aged_ordered_container.h @@ -39,22 +39,23 @@ struct IsBoostReverseIterator> : std::tru explicit IsBoostReverseIterator() = default; }; -/** Associative container where each element is also indexed by time. - - This container mirrors the interface of the standard library ordered - associative containers, with the addition that each element is associated - with a `when` `time_point` which is obtained from the value of the clock's - `now`. The function `touch` updates the time for an element to the current - time as reported by the clock. - - An extra set of iterator types and member functions are provided in the - `chronological` memberspace that allow traversal in temporal or reverse - temporal order. This container is useful as a building block for caches - whose items expire after a certain amount of time. The chronological - iterators allow for fully customizable expiration strategies. - - @see aged_set, aged_multiset, aged_map, aged_multimap -*/ +/** + * Associative container where each element is also indexed by time. + * + * This container mirrors the interface of the standard library ordered + * associative containers, with the addition that each element is associated + * with a `when` `time_point` which is obtained from the value of the clock's + * `now`. The function `touch` updates the time for an element to the current + * time as reported by the clock. + * + * An extra set of iterator types and member functions are provided in the + * `chronological` memberspace that allow traversal in temporal or reverse + * temporal order. This container is useful as a building block for caches + * whose items expire after a certain amount of time. The chronological + * iterators allow for fully customizable expiration strategies. + * + * @see aged_set, aged_multiset, aged_map, aged_multimap + */ template < bool IsMulti, bool IsMap, @@ -359,6 +360,7 @@ private: deleteElement(Element const* p) { ElementAllocatorTraits::destroy(config_.alloc(), p); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) ElementAllocatorTraits::deallocate(config_.alloc(), const_cast(p), 1); } @@ -1794,7 +1796,9 @@ swap( lhs.swap(rhs); } -/** Expire aged container items past the specified age. */ +/** + * Expire aged container items past the specified age. + */ template < bool IsMulti, bool IsMap, diff --git a/include/xrpl/beast/container/detail/aged_unordered_container.h b/include/xrpl/beast/container/detail/aged_unordered_container.h index ea24141bfb..db10e8cc23 100644 --- a/include/xrpl/beast/container/detail/aged_unordered_container.h +++ b/include/xrpl/beast/container/detail/aged_unordered_container.h @@ -43,23 +43,24 @@ TODO namespace beast { namespace detail { -/** Associative container where each element is also indexed by time. - - This container mirrors the interface of the standard library unordered - associative containers, with the addition that each element is associated - with a `when` `time_point` which is obtained from the value of the clock's - `now`. The function `touch` updates the time for an element to the current - time as reported by the clock. - - An extra set of iterator types and member functions are provided in the - `chronological` memberspace that allow traversal in temporal or reverse - temporal order. This container is useful as a building block for caches - whose items expire after a certain amount of time. The chronological - iterators allow for fully customizable expiration strategies. - - @see aged_unordered_set, aged_unordered_multiset - @see aged_unordered_map, aged_unordered_multimap -*/ +/** + * Associative container where each element is also indexed by time. + * + * This container mirrors the interface of the standard library unordered + * associative containers, with the addition that each element is associated + * with a `when` `time_point` which is obtained from the value of the clock's + * `now`. The function `touch` updates the time for an element to the current + * time as reported by the clock. + * + * An extra set of iterator types and member functions are provided in the + * `chronological` memberspace that allow traversal in temporal or reverse + * temporal order. This container is useful as a building block for caches + * whose items expire after a certain amount of time. The chronological + * iterators allow for fully customizable expiration strategies. + * + * @see aged_unordered_set, aged_unordered_multiset + * @see aged_unordered_map, aged_unordered_multimap + */ template < bool IsMulti, bool IsMap, @@ -528,6 +529,7 @@ private: deleteElement(Element const* p) { ElementAllocatorTraits::destroy(config_.alloc(), p); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) ElementAllocatorTraits::deallocate(config_.alloc(), const_cast(p), 1); } @@ -2708,7 +2710,9 @@ swap( lhs.swap(rhs); } -/** Expire aged container items past the specified age. */ +/** + * Expire aged container items past the specified age. + */ template < bool IsMulti, bool IsMap, diff --git a/include/xrpl/beast/core/CurrentThreadName.h b/include/xrpl/beast/core/CurrentThreadName.h index 3cdfe4c678..d1f14a6f80 100644 --- a/include/xrpl/beast/core/CurrentThreadName.h +++ b/include/xrpl/beast/core/CurrentThreadName.h @@ -12,9 +12,10 @@ namespace beast { -/** Changes the name of the caller thread. - Different OSes may place different length or content limits on this name. -*/ +/** + * Changes the name of the caller thread. + * Different OSes may place different length or content limits on this name. + */ void setCurrentThreadName(std::string_view newThreadName); @@ -24,13 +25,14 @@ setCurrentThreadName(std::string_view newThreadName); // Maximum number of characters is therefore 15. constexpr std::size_t kMaxThreadNameLength = 15; -/** Sets the name of the caller thread with compile-time size checking. - @tparam N The size of the string literal including null terminator - @param newThreadName A string literal to set as the thread name - - This template overload enforces that thread names are at most 16 characters - (including null terminator) at compile time, matching Linux's limit. -*/ +/** + * Sets the name of the caller thread with compile-time size checking. + * @tparam N The size of the string literal including null terminator + * @param newThreadName A string literal to set as the thread name + * + * This template overload enforces that thread names are at most 16 characters + * (including null terminator) at compile time, matching Linux's limit. + */ template void setCurrentThreadName(char const (&newThreadName)[N]) @@ -41,14 +43,15 @@ setCurrentThreadName(char const (&newThreadName)[N]) } #endif -/** Returns the name of the caller thread. - - The name returned is the name as set by a call to setCurrentThreadName(). - If the thread name is set by an external force, then that name change - will not be reported. - - If no name has ever been set, then the empty string is returned. -*/ +/** + * Returns the name of the caller thread. + * + * The name returned is the name as set by a call to setCurrentThreadName(). + * If the thread name is set by an external force, then that name change + * will not be reported. + * + * If no name has ever been set, then the empty string is returned. + */ std::string getCurrentThreadName(); diff --git a/include/xrpl/beast/core/LexicalCast.h b/include/xrpl/beast/core/LexicalCast.h index 1162d83078..7cf21892bd 100644 --- a/include/xrpl/beast/core/LexicalCast.h +++ b/include/xrpl/beast/core/LexicalCast.h @@ -163,17 +163,19 @@ struct LexicalCast //------------------------------------------------------------------------------ -/** Thrown when a conversion is not possible with LexicalCast. - Only used in the throw variants of lexicalCast. -*/ +/** + * Thrown when a conversion is not possible with LexicalCast. + * Only used in the throw variants of lexicalCast. + */ struct BadLexicalCast : public std::bad_cast { explicit BadLexicalCast() = default; }; -/** Intelligently convert from one type to another. - @return `false` if there was a parsing or range error -*/ +/** + * Intelligently convert from one type to another. + * @return `false` if there was a parsing or range error + */ template bool lexicalCastChecked(Out& out, In in) @@ -181,12 +183,13 @@ lexicalCastChecked(Out& out, In in) return detail::LexicalCast()(out, in); } -/** Convert from one type to another, throw on error - - An exception of type BadLexicalCast is thrown if the conversion fails. - - @return The new type. -*/ +/** + * Convert from one type to another, throw on error + * + * An exception of type BadLexicalCast is thrown if the conversion fails. + * + * @return The new type. + */ template Out lexicalCastThrow(In in) @@ -197,11 +200,12 @@ lexicalCastThrow(In in) throw BadLexicalCast(); } -/** Convert from one type to another. - - @param defaultValue The value returned if parsing fails - @return The new type. -*/ +/** + * Convert from one type to another. + * + * @param defaultValue The value returned if parsing fails + * @return The new type. + */ template Out lexicalCast(In in, Out defaultValue = Out()) diff --git a/include/xrpl/beast/core/List.h b/include/xrpl/beast/core/List.h index 1eeeaa87d1..b9b6829d31 100644 --- a/include/xrpl/beast/core/List.h +++ b/include/xrpl/beast/core/List.h @@ -11,7 +11,9 @@ class List; namespace detail { -/** Copy `const` attribute from T to U if present. */ +/** + * Copy `const` attribute from T to U if present. + */ /** @{ */ template struct CopyConst @@ -153,110 +155,111 @@ private: } // namespace detail -/** Intrusive doubly linked list. - - This intrusive List is a container similar in operation to std::list in the - Standard Template Library (STL). Like all @ref intrusive containers, List - requires you to first derive your class from List<>::Node: - - @code - - struct Object : List ::Node - { - explicit Object (int value) : value_ (value) - { - } - - int value_; - }; - - @endcode - - Now we define the list, and add a couple of items. - - @code - - List list; - - list.push_back (* (new Object (1))); - list.push_back (* (new Object (2))); - - @endcode - - For compatibility with the standard containers, push_back() expects a - reference to the object. Unlike the standard container, however, push_back() - places the actual object in the list and not a copy-constructed duplicate. - - Iterating over the list follows the same idiom as the STL: - - @code - - for (List ::iterator iter = list.begin(); iter != list.end; ++iter) - std::cout << iter->value_; - - @endcode - - You can even use BOOST_FOREACH, or range based for loops: - - @code - - BOOST_FOREACH (Object& object, list) // boost only - std::cout << object.value_; - - for (Object& object : list) // C++11 only - std::cout << object.value_; - - @endcode - - Because List is mostly STL compliant, it can be passed into STL algorithms: - e.g. `std::for_each()` or `std::find_first_of()`. - - In general, objects placed into a List should be dynamically allocated - although this cannot be enforced at compile time. Since the caller provides - the storage for the object, the caller is also responsible for deleting the - object. An object still exists after being removed from a List, until the - caller deletes it. This means an element can be moved from one List to - another with practically no overhead. - - Unlike the standard containers, an object may only exist in one list at a - time, unless special preparations are made. The Tag template parameter is - used to distinguish between different list types for the same object, - allowing the object to exist in more than one list simultaneously. - - For example, consider an actor system where a global list of actors is - maintained, so that they can each be periodically receive processing - time. We wish to also maintain a list of the subset of actors that require - a domain-dependent update. To achieve this, we declare two tags, the - associated list types, and the list element thusly: - - @code - - struct Actor; // Forward declaration required - - struct ProcessTag { }; - struct UpdateTag { }; - - using ProcessList = List ; - using UpdateList = List ; - - // Derive from both node types so we can be in each list at once. - // - struct Actor : ProcessList::Node, UpdateList::Node - { - bool process (); // returns true if we need an update - void update (); - }; - - @endcode - - @tparam T The base type of element which the list will store - pointers to. - - @tparam Tag An optional unique type name used to distinguish lists and - nodes, when the object can exist in multiple lists simultaneously. - - @ingroup beast_core intrusive -*/ +/** + * Intrusive doubly linked list. + * + * This intrusive List is a container similar in operation to std::list in the + * Standard Template Library (STL). Like all @ref intrusive containers, List + * requires you to first derive your class from List<>::Node: + * + * @code + * + * struct Object : List ::Node + * { + * explicit Object (int value) : value_ (value) + * { + * } + * + * int value_; + * }; + * + * @endcode + * + * Now we define the list, and add a couple of items. + * + * @code + * + * List list; + * + * list.push_back (* (new Object (1))); + * list.push_back (* (new Object (2))); + * + * @endcode + * + * For compatibility with the standard containers, push_back() expects a + * reference to the object. Unlike the standard container, however, push_back() + * places the actual object in the list and not a copy-constructed duplicate. + * + * Iterating over the list follows the same idiom as the STL: + * + * @code + * + * for (List ::iterator iter = list.begin(); iter != list.end; ++iter) + * std::cout << iter->value_; + * + * @endcode + * + * You can even use BOOST_FOREACH, or range based for loops: + * + * @code + * + * BOOST_FOREACH (Object& object, list) // boost only + * std::cout << object.value_; + * + * for (Object& object : list) // C++11 only + * std::cout << object.value_; + * + * @endcode + * + * Because List is mostly STL compliant, it can be passed into STL algorithms: + * e.g. `std::for_each()` or `std::find_first_of()`. + * + * In general, objects placed into a List should be dynamically allocated + * although this cannot be enforced at compile time. Since the caller provides + * the storage for the object, the caller is also responsible for deleting the + * object. An object still exists after being removed from a List, until the + * caller deletes it. This means an element can be moved from one List to + * another with practically no overhead. + * + * Unlike the standard containers, an object may only exist in one list at a + * time, unless special preparations are made. The Tag template parameter is + * used to distinguish between different list types for the same object, + * allowing the object to exist in more than one list simultaneously. + * + * For example, consider an actor system where a global list of actors is + * maintained, so that they can each be periodically receive processing + * time. We wish to also maintain a list of the subset of actors that require + * a domain-dependent update. To achieve this, we declare two tags, the + * associated list types, and the list element thusly: + * + * @code + * + * struct Actor; // Forward declaration required + * + * struct ProcessTag { }; + * struct UpdateTag { }; + * + * using ProcessList = List ; + * using UpdateList = List ; + * + * // Derive from both node types so we can be in each list at once. + * // + * struct Actor : ProcessList::Node, UpdateList::Node + * { + * bool process (); // returns true if we need an update + * void update (); + * }; + * + * @endcode + * + * @tparam T The base type of element which the list will store + * pointers to. + * + * @tparam Tag An optional unique type name used to distinguish lists and + * nodes, when the object can exist in multiple lists simultaneously. + * + * @ingroup beast_core intrusive + */ template class List { @@ -274,7 +277,9 @@ public: using iterator = detail::ListIterator; using const_iterator = detail::ListIterator; - /** Create an empty list. */ + /** + * Create an empty list. + */ List() { head_.prev_ = nullptr; // identifies the head @@ -286,119 +291,133 @@ public: List& operator=(List const&) = delete; - /** Determine if the list is empty. - @return `true` if the list is empty. - */ + /** + * Determine if the list is empty. + * @return `true` if the list is empty. + */ [[nodiscard]] bool empty() const noexcept { return size() == 0; } - /** Returns the number of elements in the list. */ + /** + * Returns the number of elements in the list. + */ [[nodiscard]] size_type size() const noexcept { return size_; } - /** Obtain a reference to the first element. - @invariant The list may not be empty. - @return A reference to the first element. - */ + /** + * Obtain a reference to the first element. + * @invariant The list may not be empty. + * @return A reference to the first element. + */ reference front() noexcept { return element_from(head_.next_); } - /** Obtain a const reference to the first element. - @invariant The list may not be empty. - @return A const reference to the first element. - */ + /** + * Obtain a const reference to the first element. + * @invariant The list may not be empty. + * @return A const reference to the first element. + */ [[nodiscard]] const_reference front() const noexcept { return element_from(head_.next_); } - /** Obtain a reference to the last element. - @invariant The list may not be empty. - @return A reference to the last element. - */ + /** + * Obtain a reference to the last element. + * @invariant The list may not be empty. + * @return A reference to the last element. + */ reference back() noexcept { return element_from(tail_.prev_); } - /** Obtain a const reference to the last element. - @invariant The list may not be empty. - @return A const reference to the last element. - */ + /** + * Obtain a const reference to the last element. + * @invariant The list may not be empty. + * @return A const reference to the last element. + */ [[nodiscard]] const_reference back() const noexcept { return element_from(tail_.prev_); } - /** Obtain an iterator to the beginning of the list. - @return An iterator pointing to the beginning of the list. - */ + /** + * Obtain an iterator to the beginning of the list. + * @return An iterator pointing to the beginning of the list. + */ iterator begin() noexcept { return iterator(head_.next_); } - /** Obtain a const iterator to the beginning of the list. - @return A const iterator pointing to the beginning of the list. - */ + /** + * Obtain a const iterator to the beginning of the list. + * @return A const iterator pointing to the beginning of the list. + */ [[nodiscard]] const_iterator begin() const noexcept { return const_iterator(head_.next_); } - /** Obtain a const iterator to the beginning of the list. - @return A const iterator pointing to the beginning of the list. - */ + /** + * Obtain a const iterator to the beginning of the list. + * @return A const iterator pointing to the beginning of the list. + */ [[nodiscard]] const_iterator cbegin() const noexcept { return const_iterator(head_.next_); } - /** Obtain a iterator to the end of the list. - @return An iterator pointing to the end of the list. - */ + /** + * Obtain a iterator to the end of the list. + * @return An iterator pointing to the end of the list. + */ iterator end() noexcept { return iterator(&tail_); } - /** Obtain a const iterator to the end of the list. - @return A constiterator pointing to the end of the list. - */ + /** + * Obtain a const iterator to the end of the list. + * @return A constiterator pointing to the end of the list. + */ [[nodiscard]] const_iterator end() const noexcept { return const_iterator(&tail_); } - /** Obtain a const iterator to the end of the list - @return A constiterator pointing to the end of the list. - */ + /** + * Obtain a const iterator to the end of the list + * @return A constiterator pointing to the end of the list. + */ [[nodiscard]] const_iterator cend() const noexcept { return const_iterator(&tail_); } - /** Clear the list. - @note This does not free the elements. - */ + /** + * Clear the list. + * @note This does not free the elements. + */ void clear() noexcept { @@ -407,12 +426,13 @@ public: size_ = 0; } - /** Insert an element. - @invariant The element must not already be in the list. - @param pos The location to insert after. - @param element The element to insert. - @return An iterator pointing to the newly inserted element. - */ + /** + * Insert an element. + * @invariant The element must not already be in the list. + * @param pos The location to insert after. + * @param element The element to insert. + * @return An iterator pointing to the newly inserted element. + */ iterator insert(iterator pos, T& element) noexcept { @@ -425,11 +445,12 @@ public: return iterator(node); } - /** Insert another list into this one. - The other list is cleared. - @param pos The location to insert after. - @param other The list to insert. - */ + /** + * Insert another list into this one. + * The other list is cleared. + * @param pos The location to insert after. + * @param other The list to insert. + */ void insert(iterator pos, List& other) noexcept { @@ -445,11 +466,12 @@ public: } } - /** Remove an element. - @invariant The element must exist in the list. - @param pos An iterator pointing to the element to remove. - @return An iterator pointing to the next element after the one removed. - */ + /** + * Remove an element. + * @invariant The element must exist in the list. + * @param pos An iterator pointing to the element to remove. + * @return An iterator pointing to the next element after the one removed. + */ iterator erase(iterator pos) noexcept { @@ -461,20 +483,22 @@ public: return pos; } - /** Insert an element at the beginning of the list. - @invariant The element must not exist in the list. - @param element The element to insert. - */ + /** + * Insert an element at the beginning of the list. + * @invariant The element must not exist in the list. + * @param element The element to insert. + */ iterator pushFront(T& element) noexcept { return insert(begin(), element); } - /** Remove the element at the beginning of the list. - @invariant The list must not be empty. - @return A reference to the popped element. - */ + /** + * Remove the element at the beginning of the list. + * @invariant The list must not be empty. + * @return A reference to the popped element. + */ T& popFront() noexcept { @@ -483,20 +507,22 @@ public: return element; } - /** Append an element at the end of the list. - @invariant The element must not exist in the list. - @param element The element to append. - */ + /** + * Append an element at the end of the list. + * @invariant The element must not exist in the list. + * @param element The element to append. + */ iterator pushBack(T& element) noexcept { return insert(end(), element); } - /** Remove the element at the end of the list. - @invariant The list must not be empty. - @return A reference to the popped element. - */ + /** + * Remove the element at the end of the list. + * @invariant The list must not be empty. + * @return A reference to the popped element. + */ T& popBack() noexcept { @@ -505,7 +531,9 @@ public: return element; } - /** Swap contents with another list. */ + /** + * Swap contents with another list. + */ void swap(List& other) noexcept { @@ -515,42 +543,46 @@ public: append(temp); } - /** Insert another list at the beginning of this list. - The other list is cleared. - @param list The other list to insert. - */ + /** + * Insert another list at the beginning of this list. + * The other list is cleared. + * @param list The other list to insert. + */ iterator prepend(List& list) noexcept { return insert(begin(), list); } - /** Append another list at the end of this list. - The other list is cleared. - @param list the other list to append. - */ + /** + * Append another list at the end of this list. + * The other list is cleared. + * @param list the other list to append. + */ iterator append(List& list) noexcept { return insert(end(), list); } - /** Obtain an iterator from an element. - @invariant The element must exist in the list. - @param element The element to obtain an iterator for. - @return An iterator to the element. - */ + /** + * Obtain an iterator from an element. + * @invariant The element must exist in the list. + * @param element The element to obtain an iterator for. + * @return An iterator to the element. + */ iterator iteratorTo(T& element) const noexcept { return iterator(static_cast(&element)); } - /** Obtain a const iterator from an element. - @invariant The element must exist in the list. - @param element The element to obtain an iterator for. - @return A const iterator to the element. - */ + /** + * Obtain a const iterator from an element. + * @invariant The element must exist in the list. + * @param element The element to obtain an iterator for. + * @return A const iterator to the element. + */ [[nodiscard]] const_iterator constIteratorTo(T const& element) const noexcept { diff --git a/include/xrpl/beast/core/LockFreeStack.h b/include/xrpl/beast/core/LockFreeStack.h index dd135d2d98..849edc8fce 100644 --- a/include/xrpl/beast/core/LockFreeStack.h +++ b/include/xrpl/beast/core/LockFreeStack.h @@ -103,18 +103,19 @@ operator!=( //------------------------------------------------------------------------------ -/** Multiple Producer, Multiple Consumer (MPMC) intrusive stack. - - This stack is implemented using the same intrusive interface as List. - All mutations are lock-free. - - The caller is responsible for preventing the "ABA" problem: - http://en.wikipedia.org/wiki/ABA_problem - - @param Tag A type name used to distinguish lists and nodes, for - putting objects in multiple lists. If this parameter is - omitted, the default tag is used. -*/ +/** + * Multiple Producer, Multiple Consumer (MPMC) intrusive stack. + * + * This stack is implemented using the same intrusive interface as List. + * All mutations are lock-free. + * + * The caller is responsible for preventing the "ABA" problem: + * http://en.wikipedia.org/wiki/ABA_problem + * + * @param Tag A type name used to distinguish lists and nodes, for + * putting objects in multiple lists. If this parameter is + * omitted, the default tag is used. + */ template class LockFreeStack { @@ -162,24 +163,27 @@ public: LockFreeStack& operator=(LockFreeStack const&) = delete; - /** Returns true if the stack is empty. */ + /** + * Returns true if the stack is empty. + */ [[nodiscard]] bool empty() const { return head_.load() == &end_; } - /** Push a node onto the stack. - The caller is responsible for preventing the ABA problem. - This operation is lock-free. - Thread safety: - Safe to call from any thread. - - @param node The node to push. - - @return `true` if the stack was previously empty. If multiple threads - are attempting to push, only one will receive `true`. - */ + /** + * Push a node onto the stack. + * The caller is responsible for preventing the ABA problem. + * This operation is lock-free. + * Thread safety: + * Safe to call from any thread. + * + * @param node The node to push. + * + * @return `true` if the stack was previously empty. If multiple threads + * are attempting to push, only one will receive `true`. + */ // VFALCO NOTE Fix this, shouldn't it be a reference like intrusive list? bool pushFront(Node* node) @@ -195,15 +199,16 @@ public: return first; } - /** Pop an element off the stack. - The caller is responsible for preventing the ABA problem. - This operation is lock-free. - Thread safety: - Safe to call from any thread. - - @return The element that was popped, or `nullptr` if the stack - was empty. - */ + /** + * Pop an element off the stack. + * The caller is responsible for preventing the ABA problem. + * This operation is lock-free. + * Thread safety: + * Safe to call from any thread. + * + * @return The element that was popped, or `nullptr` if the stack + * was empty. + */ Element* popFront() { @@ -219,12 +224,13 @@ public: return static_cast(node); } - /** Return a forward iterator to the beginning or end of the stack. - Undefined behavior results if push_front or pop_front is called - while an iteration is in progress. - Thread safety: - Caller is responsible for synchronization. - */ + /** + * Return a forward iterator to the beginning or end of the stack. + * Undefined behavior results if push_front or pop_front is called + * while an iteration is in progress. + * Thread safety: + * Caller is responsible for synchronization. + */ /** @{ */ iterator begin() diff --git a/include/xrpl/beast/core/SemanticVersion.h b/include/xrpl/beast/core/SemanticVersion.h index 826a43d3f8..338942c252 100644 --- a/include/xrpl/beast/core/SemanticVersion.h +++ b/include/xrpl/beast/core/SemanticVersion.h @@ -6,13 +6,14 @@ namespace beast { -/** A Semantic Version number. - - Identifies the build of a particular version of software using - the Semantic Versioning Specification described here: - - http://semver.org/ -*/ +/** + * A Semantic Version number. + * + * Identifies the build of a particular version of software using + * the Semantic Versioning Specification described here: + * + * http://semver.org/ + */ class SemanticVersion { public: @@ -29,14 +30,17 @@ public: SemanticVersion(std::string_view version); - /** Parse a semantic version string. - The parsing is as strict as possible. - @return `true` if the string was parsed. - */ + /** + * Parse a semantic version string. + * The parsing is as strict as possible. + * @return `true` if the string was parsed. + */ bool parse(std::string_view input); - /** Produce a string from semantic version components. */ + /** + * Produce a string from semantic version components. + */ [[nodiscard]] std::string print() const; @@ -52,9 +56,10 @@ public: } }; -/** Compare two SemanticVersions against each other. - The comparison follows the rules as per the specification. -*/ +/** + * Compare two SemanticVersions against each other. + * The comparison follows the rules as per the specification. + */ int compare(SemanticVersion const& lhs, SemanticVersion const& rhs); diff --git a/include/xrpl/beast/hash/hash_append.h b/include/xrpl/beast/hash/hash_append.h index 3592ffbfe8..c5374f95e5 100644 --- a/include/xrpl/beast/hash/hash_append.h +++ b/include/xrpl/beast/hash/hash_append.h @@ -135,19 +135,20 @@ struct IsUniquelyRepresented> explicit IsUniquelyRepresented() = default; }; -/** Metafunction returning `true` if the type can be hashed in one call. - - For `IsContiguouslyHashable::value` to be true, then for every - combination of possible values of `T` held in `x` and `y`, - if `x == y`, then it must be true that `memcmp(&x, &y, sizeof(T))` - return 0; i.e. that `x` and `y` are represented by the same bit pattern. - - For example: A two's complement `int` should be contiguously hashable. - Every bit pattern produces a unique value that does not compare equal to - any other bit pattern's value. A IEEE floating point should not be - contiguously hashable because -0. and 0. have different bit patterns, - though they compare equal. -*/ +/** + * Metafunction returning `true` if the type can be hashed in one call. + * + * For `IsContiguouslyHashable::value` to be true, then for every + * combination of possible values of `T` held in `x` and `y`, + * if `x == y`, then it must be true that `memcmp(&x, &y, sizeof(T))` + * return 0; i.e. that `x` and `y` are represented by the same bit pattern. + * + * For example: A two's complement `int` should be contiguously hashable. + * Every bit pattern produces a unique value that does not compare equal to + * any other bit pattern's value. A IEEE floating point should not be + * contiguously hashable because -0. and 0. have different bit patterns, + * though they compare equal. + */ /** @{ */ template struct IsContiguouslyHashable @@ -172,29 +173,30 @@ struct IsContiguouslyHashable //------------------------------------------------------------------------------ -/** Logically concatenate input data to a `Hasher`. - - Hasher requirements: - - `X` is the type `Hasher` - `h` is a value of type `x` - `p` is a value convertible to `void const*` - `n` is a value of type `std::size_t`, greater than zero - - Expression: - `h.append (p, n);` - Throws: - Never - Effect: - Adds the input data to the hasher state. - - Expression: - `static_cast(j)` - Throws: - Never - Effect: - Returns the resulting hash of all the input data. -*/ +/** + * Logically concatenate input data to a `Hasher`. + * + * Hasher requirements: + * + * `X` is the type `Hasher` + * `h` is a value of type `x` + * `p` is a value convertible to `void const*` + * `n` is a value of type `std::size_t`, greater than zero + * + * Expression: + * `h.append (p, n);` + * Throws: + * Never + * Effect: + * Adds the input data to the hasher state. + * + * Expression: + * `static_cast(j)` + * Throws: + * Never + * Effect: + * Returns the resulting hash of all the input data. + */ /** @{ */ // scalars diff --git a/include/xrpl/beast/insight/Collector.h b/include/xrpl/beast/insight/Collector.h index 3f83e329d4..9da2a8bb74 100644 --- a/include/xrpl/beast/insight/Collector.h +++ b/include/xrpl/beast/insight/Collector.h @@ -12,16 +12,17 @@ namespace beast::insight { -/** Interface for a manager that allows collection of metrics. - - To export metrics from a class, pass and save a shared_ptr to this - interface in the class constructor. Create the metric objects - as desired (counters, events, gauges, meters, and an optional hook) - using the interface. - - @see Counter, Event, Gauge, Hook, Meter - @see NullCollector, StatsDCollector -*/ +/** + * Interface for a manager that allows collection of metrics. + * + * To export metrics from a class, pass and save a shared_ptr to this + * interface in the class constructor. Create the metric objects + * as desired (counters, events, gauges, meters, and an optional hook) + * using the interface. + * + * @see Counter, Event, Gauge, Hook, Meter + * @see NullCollector, StatsDCollector + */ class Collector { public: @@ -29,18 +30,19 @@ public: virtual ~Collector() = 0; - /** Create a hook. - - A hook is called at each collection interval, on an implementation - defined thread. This is a convenience facility for gathering metrics - in the polling style. The typical usage is to update all the metrics - of interest in the handler. - - Handler will be called with this signature: - void handler (void) - - @see Hook - */ + /** + * Create a hook. + * + * A hook is called at each collection interval, on an implementation + * defined thread. This is a convenience facility for gathering metrics + * in the polling style. The typical usage is to update all the metrics + * of interest in the handler. + * + * Handler will be called with this signature: + * void handler (void) + * + * @see Hook + */ /** @{ */ template Hook @@ -53,9 +55,10 @@ public: makeHook(HookImpl::HandlerType const& handler) = 0; /** @} */ - /** Create a counter with the specified name. - @see Counter - */ + /** + * Create a counter with the specified name. + * @see Counter + */ /** @{ */ virtual Counter makeCounter(std::string const& name) = 0; @@ -69,9 +72,10 @@ public: } /** @} */ - /** Create an event with the specified name. - @see Event - */ + /** + * Create an event with the specified name. + * @see Event + */ /** @{ */ virtual Event makeEvent(std::string const& name) = 0; @@ -85,9 +89,10 @@ public: } /** @} */ - /** Create a gauge with the specified name. - @see Gauge - */ + /** + * Create a gauge with the specified name. + * @see Gauge + */ /** @{ */ virtual Gauge makeGauge(std::string const& name) = 0; @@ -101,9 +106,10 @@ public: } /** @} */ - /** Create a meter with the specified name. - @see Meter - */ + /** + * Create a meter with the specified name. + * @see Meter + */ /** @{ */ virtual Meter makeMeter(std::string const& name) = 0; diff --git a/include/xrpl/beast/insight/Counter.h b/include/xrpl/beast/insight/Counter.h index 482808b2c7..875fadf33a 100644 --- a/include/xrpl/beast/insight/Counter.h +++ b/include/xrpl/beast/insight/Counter.h @@ -7,34 +7,39 @@ namespace beast::insight { -/** A metric for measuring an integral value. - - A counter is a gauge calculated at the server. The owner of the counter - may increment and decrement the value by an amount. - - This is a lightweight reference wrapper which is cheap to copy and assign. - When the last reference goes away, the metric is no longer collected. -*/ +/** + * A metric for measuring an integral value. + * + * A counter is a gauge calculated at the server. The owner of the counter + * may increment and decrement the value by an amount. + * + * This is a lightweight reference wrapper which is cheap to copy and assign. + * When the last reference goes away, the metric is no longer collected. + */ class Counter final { public: using value_type = CounterImpl::value_type; - /** Create a null metric. - A null metric reports no information. - */ + /** + * Create a null metric. + * A null metric reports no information. + */ Counter() = default; - /** Create the metric reference the specified implementation. - Normally this won't be called directly. Instead, call the appropriate - factory function in the Collector interface. - @see Collector. - */ + /** + * Create the metric reference the specified implementation. + * Normally this won't be called directly. Instead, call the appropriate + * factory function in the Collector interface. + * @see Collector. + */ explicit Counter(std::shared_ptr impl) : impl_(std::move(impl)) { } - /** Increment the counter. */ + /** + * Increment the counter. + */ /** @{ */ void increment(value_type amount) const diff --git a/include/xrpl/beast/insight/Event.h b/include/xrpl/beast/insight/Event.h index afccf9baba..c3ff1a8877 100644 --- a/include/xrpl/beast/insight/Event.h +++ b/include/xrpl/beast/insight/Event.h @@ -8,35 +8,40 @@ namespace beast::insight { -/** A metric for reporting event timing. - - An event is an operation that has an associated millisecond time, or - other integral value. Because events happen at a specific moment, the - metric only supports a push-style interface. - - This is a lightweight reference wrapper which is cheap to copy and assign. - When the last reference goes away, the metric is no longer collected. -*/ +/** + * A metric for reporting event timing. + * + * An event is an operation that has an associated millisecond time, or + * other integral value. Because events happen at a specific moment, the + * metric only supports a push-style interface. + * + * This is a lightweight reference wrapper which is cheap to copy and assign. + * When the last reference goes away, the metric is no longer collected. + */ class Event final { public: using value_type = EventImpl::value_type; - /** Create a null metric. - A null metric reports no information. - */ + /** + * Create a null metric. + * A null metric reports no information. + */ Event() = default; - /** Create the metric reference the specified implementation. - Normally this won't be called directly. Instead, call the appropriate - factory function in the Collector interface. - @see Collector. - */ + /** + * Create the metric reference the specified implementation. + * Normally this won't be called directly. Instead, call the appropriate + * factory function in the Collector interface. + * @see Collector. + */ explicit Event(std::shared_ptr impl) : impl_(std::move(impl)) { } - /** Push an event notification. */ + /** + * Push an event notification. + */ template void notify(std::chrono::duration const& value) const diff --git a/include/xrpl/beast/insight/Gauge.h b/include/xrpl/beast/insight/Gauge.h index 9a23ea6665..ef62e252b3 100644 --- a/include/xrpl/beast/insight/Gauge.h +++ b/include/xrpl/beast/insight/Gauge.h @@ -7,40 +7,44 @@ namespace beast::insight { -/** A metric for measuring an integral value. - - A gauge is an instantaneous measurement of a value, like the gas gauge - in a car. The caller directly sets the value, or adjusts it by a - specified amount. The value is kept in the client rather than the collector. - - This is a lightweight reference wrapper which is cheap to copy and assign. - When the last reference goes away, the metric is no longer collected. -*/ +/** + * A metric for measuring an integral value. + * + * A gauge is an instantaneous measurement of a value, like the gas gauge + * in a car. The caller directly sets the value, or adjusts it by a + * specified amount. The value is kept in the client rather than the collector. + * + * This is a lightweight reference wrapper which is cheap to copy and assign. + * When the last reference goes away, the metric is no longer collected. + */ class Gauge final { public: using value_type = GaugeImpl::value_type; using difference_type = GaugeImpl::difference_type; - /** Create a null metric. - A null metric reports no information. - */ + /** + * Create a null metric. + * A null metric reports no information. + */ Gauge() = default; - /** Create the metric reference the specified implementation. - Normally this won't be called directly. Instead, call the appropriate - factory function in the Collector interface. - @see Collector. - */ + /** + * Create the metric reference the specified implementation. + * Normally this won't be called directly. Instead, call the appropriate + * factory function in the Collector interface. + * @see Collector. + */ explicit Gauge(std::shared_ptr impl) : impl_(std::move(impl)) { } - /** Set the value on the gauge. - A Collector implementation should combine multiple calls to value - changes into a single change if the calls occur within a single - collection interval. - */ + /** + * Set the value on the gauge. + * A Collector implementation should combine multiple calls to value + * changes into a single change if the calls occur within a single + * collection interval. + */ /** @{ */ void set(value_type value) const @@ -62,7 +66,9 @@ public: } /** @} */ - /** Adjust the value of the gauge. */ + /** + * Adjust the value of the gauge. + */ /** @{ */ void increment(difference_type amount) const diff --git a/include/xrpl/beast/insight/Group.h b/include/xrpl/beast/insight/Group.h index 3e0eb93452..ecf7709546 100644 --- a/include/xrpl/beast/insight/Group.h +++ b/include/xrpl/beast/insight/Group.h @@ -7,13 +7,17 @@ namespace beast::insight { -/** A collector front-end that manages a group of metrics. */ +/** + * A collector front-end that manages a group of metrics. + */ class Group : public Collector { public: using ptr = std::shared_ptr; - /** Returns the name of this group, for diagnostics. */ + /** + * Returns the name of this group, for diagnostics. + */ [[nodiscard]] virtual std::string const& name() const = 0; }; diff --git a/include/xrpl/beast/insight/Groups.h b/include/xrpl/beast/insight/Groups.h index cfe4d99bdc..77fc2d3336 100644 --- a/include/xrpl/beast/insight/Groups.h +++ b/include/xrpl/beast/insight/Groups.h @@ -8,13 +8,17 @@ namespace beast::insight { -/** A container for managing a set of metric groups. */ +/** + * A container for managing a set of metric groups. + */ class Groups { public: virtual ~Groups() = 0; - /** Find or create a new collector with a given name. */ + /** + * Find or create a new collector with a given name. + */ /** @{ */ virtual Group::ptr const& get(std::string const& name) = 0; @@ -27,7 +31,9 @@ public: /** @} */ }; -/** Create a group container that uses the specified collector. */ +/** + * Create a group container that uses the specified collector. + */ std::unique_ptr makeGroups(Collector::ptr const& collector); diff --git a/include/xrpl/beast/insight/Hook.h b/include/xrpl/beast/insight/Hook.h index 8dbe5a4be0..572a9ffcb4 100644 --- a/include/xrpl/beast/insight/Hook.h +++ b/include/xrpl/beast/insight/Hook.h @@ -7,20 +7,24 @@ namespace beast::insight { -/** A reference to a handler for performing polled collection. */ +/** + * A reference to a handler for performing polled collection. + */ class Hook final { public: - /** Create a null hook. - A null hook has no associated handler. - */ + /** + * Create a null hook. + * A null hook has no associated handler. + */ Hook() = default; - /** Create a hook referencing the specified implementation. - Normally this won't be called directly. Instead, call the appropriate - factory function in the Collector interface. - @see Collector. - */ + /** + * Create a hook referencing the specified implementation. + * Normally this won't be called directly. Instead, call the appropriate + * factory function in the Collector interface. + * @see Collector. + */ explicit Hook(std::shared_ptr impl) : impl_(std::move(impl)) { } diff --git a/include/xrpl/beast/insight/Meter.h b/include/xrpl/beast/insight/Meter.h index 25ffabd928..ac2f3a352c 100644 --- a/include/xrpl/beast/insight/Meter.h +++ b/include/xrpl/beast/insight/Meter.h @@ -7,33 +7,38 @@ namespace beast::insight { -/** A metric for measuring an integral value. - - A meter may be thought of as an increment-only counter. - - This is a lightweight reference wrapper which is cheap to copy and assign. - When the last reference goes away, the metric is no longer collected. -*/ +/** + * A metric for measuring an integral value. + * + * A meter may be thought of as an increment-only counter. + * + * This is a lightweight reference wrapper which is cheap to copy and assign. + * When the last reference goes away, the metric is no longer collected. + */ class Meter final { public: using value_type = MeterImpl::value_type; - /** Create a null metric. - A null metric reports no information. - */ + /** + * Create a null metric. + * A null metric reports no information. + */ Meter() = default; - /** Create the metric reference the specified implementation. - Normally this won't be called directly. Instead, call the appropriate - factory function in the Collector interface. - @see Collector. - */ + /** + * Create the metric reference the specified implementation. + * Normally this won't be called directly. Instead, call the appropriate + * factory function in the Collector interface. + * @see Collector. + */ explicit Meter(std::shared_ptr impl) : impl_(std::move(impl)) { } - /** Increment the meter. */ + /** + * Increment the meter. + */ /** @{ */ void increment(value_type amount) const diff --git a/include/xrpl/beast/insight/NullCollector.h b/include/xrpl/beast/insight/NullCollector.h index 67903420fa..ffafe6d6d5 100644 --- a/include/xrpl/beast/insight/NullCollector.h +++ b/include/xrpl/beast/insight/NullCollector.h @@ -6,7 +6,9 @@ namespace beast::insight { -/** A Collector which does not collect metrics. */ +/** + * A Collector which does not collect metrics. + */ class NullCollector : public Collector { public: diff --git a/include/xrpl/beast/insight/StatsDCollector.h b/include/xrpl/beast/insight/StatsDCollector.h index 9a438c48f1..e14d3a27ff 100644 --- a/include/xrpl/beast/insight/StatsDCollector.h +++ b/include/xrpl/beast/insight/StatsDCollector.h @@ -9,20 +9,22 @@ namespace beast::insight { -/** A Collector that reports metrics to a StatsD server. - Reference: - https://github.com/b/statsd_spec -*/ +/** + * A Collector that reports metrics to a StatsD server. + * Reference: + * https://github.com/b/statsd_spec + */ class StatsDCollector : public Collector { public: explicit StatsDCollector() = default; - /** Create a StatsD collector. - @param address The IP address and port of the StatsD server. - @param prefix A string pre-pended before each metric name. - @param journal Destination for logging output. - */ + /** + * Create a StatsD collector. + * @param address The IP address and port of the StatsD server. + * @param prefix A string pre-pended before each metric name. + * @param journal Destination for logging output. + */ static std::shared_ptr make(IP::Endpoint const& address, std::string const& prefix, Journal journal); }; diff --git a/include/xrpl/beast/net/IPAddress.h b/include/xrpl/beast/net/IPAddress.h index f4327b7b8a..4f4fb189a6 100644 --- a/include/xrpl/beast/net/IPAddress.h +++ b/include/xrpl/beast/net/IPAddress.h @@ -19,42 +19,54 @@ namespace IP { using Address = boost::asio::ip::address; -/** Returns the address represented as a string. */ +/** + * Returns the address represented as a string. + */ inline std::string to_string(Address const& addr) { return addr.to_string(); } -/** Returns `true` if this is a loopback address. */ +/** + * Returns `true` if this is a loopback address. + */ inline bool isLoopback(Address const& addr) { return addr.is_loopback(); } -/** Returns `true` if the address is unspecified. */ +/** + * Returns `true` if the address is unspecified. + */ inline bool isUnspecified(Address const& addr) { return addr.is_unspecified(); } -/** Returns `true` if the address is a multicast address. */ +/** + * Returns `true` if the address is a multicast address. + */ inline bool isMulticast(Address const& addr) { return addr.is_multicast(); } -/** Returns `true` if the address is a private unroutable address. */ +/** + * Returns `true` if the address is a private unroutable address. + */ inline bool isPrivate(Address const& addr) { return (addr.is_v4()) ? isPrivate(addr.to_v4()) : isPrivate(addr.to_v6()); } -/** Returns `true` if the address is a public routable address. */ +/** + * Returns `true` if the address is a public routable address. + */ inline bool isPublic(Address const& addr) { diff --git a/include/xrpl/beast/net/IPAddressConversion.h b/include/xrpl/beast/net/IPAddressConversion.h index b5fb697233..73777cf841 100644 --- a/include/xrpl/beast/net/IPAddressConversion.h +++ b/include/xrpl/beast/net/IPAddressConversion.h @@ -6,23 +6,29 @@ namespace beast::IP { -/** Convert to Endpoint. - The port is set to zero. -*/ +/** + * Convert to Endpoint. + * The port is set to zero. + */ Endpoint fromAsio(boost::asio::ip::address const& address); -/** Convert to Endpoint. */ +/** + * Convert to Endpoint. + */ Endpoint fromAsio(boost::asio::ip::tcp::endpoint const& endpoint); -/** Convert to asio::ip::address. - The port is ignored. -*/ +/** + * Convert to asio::ip::address. + * The port is ignored. + */ boost::asio::ip::address toAsioAddress(Endpoint const& endpoint); -/** Convert to asio::ip::tcp::endpoint. */ +/** + * Convert to asio::ip::tcp::endpoint. + */ boost::asio::ip::tcp::endpoint toAsioEndpoint(Endpoint const& endpoint); diff --git a/include/xrpl/beast/net/IPAddressV4.h b/include/xrpl/beast/net/IPAddressV4.h index 9367fbe1eb..94943af3ea 100644 --- a/include/xrpl/beast/net/IPAddressV4.h +++ b/include/xrpl/beast/net/IPAddressV4.h @@ -6,17 +6,22 @@ namespace beast::IP { using AddressV4 = boost::asio::ip::address_v4; -/** Returns `true` if the address is a private unroutable address. */ +/** + * Returns `true` if the address is a private unroutable address. + */ bool isPrivate(AddressV4 const& addr); -/** Returns `true` if the address is a public routable address. */ +/** + * Returns `true` if the address is a public routable address. + */ bool isPublic(AddressV4 const& addr); -/** Returns the address class for the given address. - @note Class 'D' represents multicast addresses (224.*.*.*). -*/ +/** + * Returns the address class for the given address. + * @note Class 'D' represents multicast addresses (224.*.*.*). + */ char getClass(AddressV4 const& address); diff --git a/include/xrpl/beast/net/IPAddressV6.h b/include/xrpl/beast/net/IPAddressV6.h index 1bfa079990..b51cb62532 100644 --- a/include/xrpl/beast/net/IPAddressV6.h +++ b/include/xrpl/beast/net/IPAddressV6.h @@ -6,11 +6,15 @@ namespace beast::IP { using AddressV6 = boost::asio::ip::address_v6; -/** Returns `true` if the address is a private unroutable address. */ +/** + * Returns `true` if the address is a private unroutable address. + */ bool isPrivate(AddressV6 const& addr); -/** Returns `true` if the address is a public routable address. */ +/** + * Returns `true` if the address is a public routable address. + */ bool isPublic(AddressV6 const& addr); diff --git a/include/xrpl/beast/net/IPEndpoint.h b/include/xrpl/beast/net/IPEndpoint.h index 0b661108f2..c4b269e9c3 100644 --- a/include/xrpl/beast/net/IPEndpoint.h +++ b/include/xrpl/beast/net/IPEndpoint.h @@ -17,51 +17,68 @@ namespace beast::IP { using Port = std::uint16_t; -/** A version-independent IP address and port combination. */ +/** + * A version-independent IP address and port combination. + */ class Endpoint { public: - /** Create an unspecified endpoint. */ + /** + * Create an unspecified endpoint. + */ Endpoint(); - /** Create an endpoint from the address and optional port. */ + /** + * Create an endpoint from the address and optional port. + */ explicit Endpoint(Address addr, Port port = 0); - /** Create an Endpoint from a string. - If the port is omitted, the endpoint will have a zero port. - @return An optional endpoint; will be `std::nullopt` on failure - */ + /** + * Create an Endpoint from a string. + * If the port is omitted, the endpoint will have a zero port. + * @return An optional endpoint; will be `std::nullopt` on failure + */ static std::optional fromStringChecked(std::string const& s); static Endpoint fromString(std::string const& s); - /** Returns a string representing the endpoint. */ + /** + * Returns a string representing the endpoint. + */ [[nodiscard]] std::string toString() const; - /** Returns the port number on the endpoint. */ + /** + * Returns the port number on the endpoint. + */ [[nodiscard]] Port port() const { return port_; } - /** Returns a new Endpoint with a different port. */ + /** + * Returns a new Endpoint with a different port. + */ [[nodiscard]] Endpoint atPort(Port port) const { return Endpoint(addr_, port); } - /** Returns the address portion of this endpoint. */ + /** + * Returns the address portion of this endpoint. + */ [[nodiscard]] Address const& address() const { return addr_; } - /** Convenience accessors for the address part. */ + /** + * Convenience accessors for the address part. + */ /** @{ */ [[nodiscard]] bool isV4() const @@ -85,7 +102,9 @@ public: } /** @} */ - /** Arithmetic comparison. */ + /** + * Arithmetic comparison. + */ /** @{ */ friend bool operator==(Endpoint const& lhs, Endpoint const& rhs); @@ -131,35 +150,45 @@ private: // Properties -/** Returns `true` if the endpoint is a loopback address. */ +/** + * Returns `true` if the endpoint is a loopback address. + */ inline bool isLoopback(Endpoint const& endpoint) { return isLoopback(endpoint.address()); } -/** Returns `true` if the endpoint is unspecified. */ +/** + * Returns `true` if the endpoint is unspecified. + */ inline bool isUnspecified(Endpoint const& endpoint) { return isUnspecified(endpoint.address()); } -/** Returns `true` if the endpoint is a multicast address. */ +/** + * Returns `true` if the endpoint is a multicast address. + */ inline bool isMulticast(Endpoint const& endpoint) { return isMulticast(endpoint.address()); } -/** Returns `true` if the endpoint is a private unroutable address. */ +/** + * Returns `true` if the endpoint is a private unroutable address. + */ inline bool isPrivate(Endpoint const& endpoint) { return isPrivate(endpoint.address()); } -/** Returns `true` if the endpoint is a public routable address. */ +/** + * Returns `true` if the endpoint is a public routable address. + */ inline bool isPublic(Endpoint const& endpoint) { @@ -168,14 +197,18 @@ isPublic(Endpoint const& endpoint) //------------------------------------------------------------------------------ -/** Returns the endpoint represented as a string. */ +/** + * Returns the endpoint represented as a string. + */ inline std::string to_string(Endpoint const& endpoint) { return endpoint.toString(); } -/** Output stream conversion. */ +/** + * Output stream conversion. + */ template OutputStream& operator<<(OutputStream& os, Endpoint const& endpoint) @@ -184,7 +217,9 @@ operator<<(OutputStream& os, Endpoint const& endpoint) return os; } -/** Input stream conversion. */ +/** + * Input stream conversion. + */ std::istream& operator>>(std::istream& is, Endpoint& endpoint); @@ -193,7 +228,9 @@ operator>>(std::istream& is, Endpoint& endpoint); //------------------------------------------------------------------------------ namespace std { -/** std::hash support. */ +/** + * std::hash support. + */ template <> struct hash<::beast::IP::Endpoint> { @@ -208,7 +245,9 @@ struct hash<::beast::IP::Endpoint> } // namespace std namespace boost { -/** boost::hash support. */ +/** + * boost::hash support. + */ template <> struct hash<::beast::IP::Endpoint> { diff --git a/include/xrpl/beast/rfc2616.h b/include/xrpl/beast/rfc2616.h index 7c681ab140..bd9a78fddb 100644 --- a/include/xrpl/beast/rfc2616.h +++ b/include/xrpl/beast/rfc2616.h @@ -30,17 +30,20 @@ struct CiEqualPred } }; -/** Returns `true` if `c` is linear white space. - - This excludes the CRLF sequence allowed for line continuations. -*/ +/** + * Returns `true` if `c` is linear white space. + * + * This excludes the CRLF sequence allowed for line continuations. + */ inline bool isLws(char c) { return c == ' ' || c == '\t'; } -/** Returns `true` if `c` is any whitespace character. */ +/** + * Returns `true` if `c` is any whitespace character. + */ inline bool isWhite(char c) { @@ -87,14 +90,15 @@ trimRight(String const& s) } // namespace detail -/** Parse a character sequence of values separated by commas. - Double quotes and escape sequences will be converted. Excess white - space, commas, double quotes, and empty elements are not copied. - Format: - #(token|quoted-string) - Reference: - http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2 -*/ +/** + * Parse a character sequence of values separated by commas. + * Double quotes and escape sequences will be converted. Excess white + * space, commas, double quotes, and empty elements are not copied. + * Format: + * #(token|quoted-string) + * Reference: + * http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2 + */ template < class FwdIt, class Result = std::vector::value_type>>, @@ -189,14 +193,15 @@ splitCommas(boost::beast::string_view const& s) //------------------------------------------------------------------------------ -/** Iterates through a comma separated list. - - Meets the requirements of ForwardIterator. - - List defined in rfc2616 2.1. - - @note Values returned may contain backslash escapes. -*/ +/** + * Iterates through a comma separated list. + * + * Meets the requirements of ForwardIterator. + * + * List defined in rfc2616 2.1. + * + * @note Values returned may contain backslash escapes. + */ class ListIterator { using iter_type = boost::string_ref::const_iterator; @@ -323,17 +328,20 @@ ListIterator::increment() } } } -/** Returns true if two strings are equal. - - A case-insensitive comparison is used. -*/ +/** + * Returns true if two strings are equal. + * + * A case-insensitive comparison is used. + */ inline bool ciEqual(boost::string_ref s1, boost::string_ref s2) { return boost::range::equal(s1, s2, detail::CiEqualPred{}); } -/** Returns a range representing the list. */ +/** + * Returns a range representing the list. + */ inline boost::iterator_range makeList(boost::string_ref const& field) { @@ -341,10 +349,11 @@ makeList(boost::string_ref const& field) ListIterator{field.begin(), field.end()}, ListIterator{field.end(), field.end()}}; } -/** Returns true if the specified token exists in the list. - - A case-insensitive comparison is used. -*/ +/** + * Returns true if the specified token exists in the list. + * + * A case-insensitive comparison is used. + */ template bool tokenInList(boost::string_ref const& value, boost::string_ref const& token) diff --git a/include/xrpl/beast/test/yield_to.h b/include/xrpl/beast/test/yield_to.h index 1a34ec436e..b3aa482dd5 100644 --- a/include/xrpl/beast/test/yield_to.h +++ b/include/xrpl/beast/test/yield_to.h @@ -19,12 +19,13 @@ namespace beast::test { -/** Mix-in to support tests using asio coroutines. - - Derive from this class and use yield_to to launch test - functions inside coroutines. This is handy for testing - asynchronous asio code. -*/ +/** + * Mix-in to support tests using asio coroutines. + * + * Derive from this class and use yield_to to launch test + * functions inside coroutines. This is handy for testing + * asynchronous asio code. + */ class EnableYieldTo { protected: @@ -38,7 +39,9 @@ private: std::size_t running_ = 0; public: - /// The type of yield context passed to functions. + /** + * The type of yield context passed to functions. + */ using yield_context = boost::asio::yield_context; explicit EnableYieldTo(std::size_t concurrency = 1) : work_(boost::asio::make_work_guard(ios_)) @@ -57,24 +60,27 @@ public: t.join(); } - /// Return the `io_context` associated with the object + /** + * Return the `io_context` associated with the object + */ boost::asio::io_context& getIoContext() { return ios_; } - /** Run one or more functions, each in a coroutine. - - This call will block until all coroutines terminate. - - Each functions should have this signature: - @code - void f(yield_context); - @endcode - - @param fn... One or more functions to invoke. - */ + /** + * Run one or more functions, each in a coroutine. + * + * This call will block until all coroutines terminate. + * + * Each functions should have this signature: + * @code + * void f(yield_context); + * @endcode + * + * @param fn... One or more functions to invoke. + */ #if BEAST_DOXYGEN template void diff --git a/include/xrpl/beast/type_name.h b/include/xrpl/beast/type_name.h index ae7b681af9..85fd9ae6a2 100644 --- a/include/xrpl/beast/type_name.h +++ b/include/xrpl/beast/type_name.h @@ -23,6 +23,7 @@ typeName() if (auto s = abi::__cxa_demangle(name.c_str(), nullptr, nullptr, nullptr)) { name = s; + // NOLINTNEXTLINE(cppcoreguidelines-no-malloc) std::free(s); } #endif diff --git a/include/xrpl/beast/unit_test/amount.h b/include/xrpl/beast/unit_test/amount.h index 3a392f393f..c1e4357753 100644 --- a/include/xrpl/beast/unit_test/amount.h +++ b/include/xrpl/beast/unit_test/amount.h @@ -10,7 +10,9 @@ namespace beast::unit_test { -/** Utility for producing nicely composed output of amounts with units. */ +/** + * Utility for producing nicely composed output of amounts with units. + */ class Amount { private: diff --git a/include/xrpl/beast/unit_test/detail/const_container.h b/include/xrpl/beast/unit_test/detail/const_container.h index 6826bf4258..9f4646cbdb 100644 --- a/include/xrpl/beast/unit_test/detail/const_container.h +++ b/include/xrpl/beast/unit_test/detail/const_container.h @@ -6,10 +6,11 @@ namespace beast::unit_test::detail { -/** Adapter to constrain a container interface. - The interface allows for limited read only operations. Derived classes - provide additional behavior. -*/ +/** + * Adapter to constrain a container interface. + * The interface allows for limited read only operations. Derived classes + * provide additional behavior. + */ template class ConstContainer { @@ -38,21 +39,27 @@ public: using iterator = cont_type::const_iterator; using const_iterator = cont_type::const_iterator; - /** Returns `true` if the container is empty. */ + /** + * Returns `true` if the container is empty. + */ [[nodiscard]] bool empty() const { return cont_.empty(); } - /** Returns the number of items in the container. */ + /** + * Returns the number of items in the container. + */ [[nodiscard]] size_type size() const { return cont_.size(); } - /** Returns forward iterators for traversal. */ + /** + * Returns forward iterators for traversal. + */ /** @{ */ [[nodiscard]] const_iterator begin() const diff --git a/include/xrpl/beast/unit_test/global_suites.h b/include/xrpl/beast/unit_test/global_suites.h index 72ed738bdb..18e5bc3a6b 100644 --- a/include/xrpl/beast/unit_test/global_suites.h +++ b/include/xrpl/beast/unit_test/global_suites.h @@ -10,7 +10,9 @@ namespace beast::unit_test { namespace detail { -/// Holds test suites registered during static initialization. +/** + * Holds test suites registered during static initialization. + */ inline SuiteList& globalSuites() { @@ -34,7 +36,9 @@ struct InsertSuite } // namespace detail -/// Holds test suites registered during static initialization. +/** + * Holds test suites registered during static initialization. + */ inline SuiteList const& globalSuites() { diff --git a/include/xrpl/beast/unit_test/match.h b/include/xrpl/beast/unit_test/match.h index 5faeaa1100..966574b833 100644 --- a/include/xrpl/beast/unit_test/match.h +++ b/include/xrpl/beast/unit_test/match.h @@ -121,42 +121,48 @@ Selector::operator()(SuiteInfo const& s) // Utility functions for producing predicates to select suites. -/** Returns a predicate that implements a smart matching rule. - The predicate checks the suite, module, and library fields of the - SuiteInfo in that order. When it finds a match, it changes modes - depending on what was found: - - If a suite is matched first, then only the suite is selected. The - suite may be marked manual. - - If a module is matched first, then only suites from that module - and library not marked manual are selected from then on. - - If a library is matched first, then only suites from that library - not marked manual are selected from then on. - -*/ +/** + * Returns a predicate that implements a smart matching rule. + * The predicate checks the suite, module, and library fields of the + * SuiteInfo in that order. When it finds a match, it changes modes + * depending on what was found: + * + * If a suite is matched first, then only the suite is selected. The + * suite may be marked manual. + * + * If a module is matched first, then only suites from that module + * and library not marked manual are selected from then on. + * + * If a library is matched first, then only suites from that library + * not marked manual are selected from then on. + */ inline Selector matchAuto(std::string const& name) { return Selector(Selector::ModeT::Automatch, name); } -/** Return a predicate that matches all suites not marked manual. */ +/** + * Return a predicate that matches all suites not marked manual. + */ inline Selector matchAll() { return Selector(Selector::ModeT::All); } -/** Returns a predicate that matches a specific suite. */ +/** + * Returns a predicate that matches a specific suite. + */ inline Selector matchSuite(std::string const& name) { return Selector(Selector::ModeT::Suite, name); } -/** Returns a predicate that matches all suites in a library. */ +/** + * Returns a predicate that matches all suites in a library. + */ inline Selector matchLibrary(std::string const& name) { diff --git a/include/xrpl/beast/unit_test/recorder.h b/include/xrpl/beast/unit_test/recorder.h index 1b7347dc2e..fcadb63fc9 100644 --- a/include/xrpl/beast/unit_test/recorder.h +++ b/include/xrpl/beast/unit_test/recorder.h @@ -13,7 +13,9 @@ namespace beast::unit_test { -/** A test runner that stores the results. */ +/** + * A test runner that stores the results. + */ class Recorder : public Runner { private: @@ -24,7 +26,9 @@ private: public: Recorder() = default; - /** Returns a report with the results of all completed suites. */ + /** + * Returns a report with the results of all completed suites. + */ [[nodiscard]] Results const& report() const { diff --git a/include/xrpl/beast/unit_test/reporter.h b/include/xrpl/beast/unit_test/reporter.h index a903d9f8c2..0fe77a7862 100644 --- a/include/xrpl/beast/unit_test/reporter.h +++ b/include/xrpl/beast/unit_test/reporter.h @@ -25,9 +25,10 @@ namespace beast::unit_test { namespace detail { -/** A simple test runner that writes everything to a stream in real time. - The totals are output when the object is destroyed. -*/ +/** + * A simple test runner that writes everything to a stream in real time. + * The totals are output when the object is destroyed. + */ template class Reporter : public Runner { diff --git a/include/xrpl/beast/unit_test/results.h b/include/xrpl/beast/unit_test/results.h index 718d764c9f..273ad5b129 100644 --- a/include/xrpl/beast/unit_test/results.h +++ b/include/xrpl/beast/unit_test/results.h @@ -13,11 +13,15 @@ namespace beast::unit_test { -/** Holds a set of test condition outcomes in a testcase. */ +/** + * Holds a set of test condition outcomes in a testcase. + */ class CaseResults { public: - /** Holds the result of evaluating one test condition. */ + /** + * Holds the result of evaluating one test condition. + */ struct Test { explicit Test(bool pass) : pass(pass) @@ -41,28 +45,36 @@ private: public: TestsT() = default; - /** Returns the total number of test conditions. */ + /** + * Returns the total number of test conditions. + */ [[nodiscard]] std::size_t total() const { return cont().size(); } - /** Returns the number of failed test conditions. */ + /** + * Returns the number of failed test conditions. + */ [[nodiscard]] std::size_t failed() const { return failed_; } - /** Register a successful test condition. */ + /** + * Register a successful test condition. + */ void pass() { cont().emplace_back(true); } - /** Register a failed test condition. */ + /** + * Register a failed test condition. + */ void fail(std::string const& reason = "") { @@ -74,7 +86,9 @@ private: class LogT : public detail::ConstContainer> { public: - /** Insert a string into the log. */ + /** + * Insert a string into the log. + */ void insert(std::string const& s) { @@ -89,23 +103,31 @@ public: { } - /** Returns the name of this testcase. */ + /** + * Returns the name of this testcase. + */ [[nodiscard]] std::string const& name() const { return name_; } - /** Memberspace for a container of test condition outcomes. */ + /** + * Memberspace for a container of test condition outcomes. + */ TestsT tests; - /** Memberspace for a container of testcase log messages. */ + /** + * Memberspace for a container of testcase log messages. + */ LogT log; }; //-------------------------------------------------------------------------- -/** Holds the set of testcase results in a suite. */ +/** + * Holds the set of testcase results in a suite. + */ class SuiteResults : public detail::ConstContainer> { private: @@ -118,28 +140,36 @@ public: { } - /** Returns the name of this suite. */ + /** + * Returns the name of this suite. + */ [[nodiscard]] std::string const& name() const { return name_; } - /** Returns the total number of test conditions. */ + /** + * Returns the total number of test conditions. + */ [[nodiscard]] std::size_t total() const { return total_; } - /** Returns the number of failures. */ + /** + * Returns the number of failures. + */ [[nodiscard]] std::size_t failed() const { return failed_; } - /** Insert a set of testcase results. */ + /** + * Insert a set of testcase results. + */ /** @{ */ void insert(CaseResults&& r) @@ -162,7 +192,9 @@ public: //------------------------------------------------------------------------------ // VFALCO TODO Make this a template class using scoped allocators -/** Holds the results of running a set of testsuites. */ +/** + * Holds the results of running a set of testsuites. + */ class Results : public detail::ConstContainer> { private: @@ -173,28 +205,36 @@ private: public: Results() = default; - /** Returns the total number of test cases. */ + /** + * Returns the total number of test cases. + */ [[nodiscard]] std::size_t cases() const { return cases_; } - /** Returns the total number of test conditions. */ + /** + * Returns the total number of test conditions. + */ [[nodiscard]] std::size_t total() const { return total_; } - /** Returns the number of failures. */ + /** + * Returns the number of failures. + */ [[nodiscard]] std::size_t failed() const { return failed_; } - /** Insert a set of suite results. */ + /** + * Insert a set of suite results. + */ /** @{ */ void insert(SuiteResults&& r) diff --git a/include/xrpl/beast/unit_test/runner.h b/include/xrpl/beast/unit_test/runner.h index b88bfc5fe1..f8f9deca48 100644 --- a/include/xrpl/beast/unit_test/runner.h +++ b/include/xrpl/beast/unit_test/runner.h @@ -13,11 +13,12 @@ namespace beast::unit_test { -/** Unit test runner interface. - - Derived classes can customize the reporting behavior. This interface is - injected into the unit_test class to receive the results of the tests. -*/ +/** + * Unit test runner interface. + * + * Derived classes can customize the reporting behavior. This interface is + * injected into the unit_test class to receive the results of the tests. + */ class Runner { std::string arg_; @@ -33,110 +34,132 @@ public: Runner& operator=(Runner const&) = delete; - /** Set the argument string. - - The argument string is available to suites and - allows for customization of the test. Each suite - defines its own syntax for the argument string. - The same argument is passed to all suites. - */ + /** + * Set the argument string. + * + * The argument string is available to suites and + * allows for customization of the test. Each suite + * defines its own syntax for the argument string. + * The same argument is passed to all suites. + */ void arg(std::string const& s) { arg_ = s; } - /** Returns the argument string. */ + /** + * Returns the argument string. + */ [[nodiscard]] std::string const& arg() const { return arg_; } - /** Run the specified suite. - @return `true` if any conditions failed. - */ + /** + * Run the specified suite. + * @return `true` if any conditions failed. + */ template bool run(SuiteInfo const& s); - /** Run a sequence of suites. - The expression - `FwdIter::value_type` - must be convertible to `SuiteInfo`. - @return `true` if any conditions failed. - */ + /** + * Run a sequence of suites. + * The expression + * `FwdIter::value_type` + * must be convertible to `SuiteInfo`. + * @return `true` if any conditions failed. + */ template bool run(FwdIter first, FwdIter last); - /** Conditionally run a sequence of suites. - pred will be called as: - @code - bool pred(SuiteInfo const&); - @endcode - @return `true` if any conditions failed. - */ + /** + * Conditionally run a sequence of suites. + * pred will be called as: + * @code + * bool pred(SuiteInfo const&); + * @endcode + * @return `true` if any conditions failed. + */ template bool runIf(FwdIter first, FwdIter last, Pred pred = Pred{}); - /** Run all suites in a container. - @return `true` if any conditions failed. - */ + /** + * Run all suites in a container. + * @return `true` if any conditions failed. + */ template bool runEach(SequenceContainer const& c); - /** Conditionally run suites in a container. - pred will be called as: - @code - bool pred(SuiteInfo const&); - @endcode - @return `true` if any conditions failed. - */ + /** + * Conditionally run suites in a container. + * pred will be called as: + * @code + * bool pred(SuiteInfo const&); + * @endcode + * @return `true` if any conditions failed. + */ template bool runEachIf(SequenceContainer const& c, Pred pred = Pred{}); protected: - /// Called when a new suite starts. + /** + * Called when a new suite starts. + */ virtual void onSuiteBegin(SuiteInfo const&) { } - /// Called when a suite ends. + /** + * Called when a suite ends. + */ virtual void onSuiteEnd() { } - /// Called when a new case starts. + /** + * Called when a new case starts. + */ virtual void onCaseBegin(std::string const&) { } - /// Called when a new case ends. + /** + * Called when a new case ends. + */ virtual void onCaseEnd() { } - /// Called for each passing condition. + /** + * Called for each passing condition. + */ virtual void onPass() { } - /// Called for each failing condition. + /** + * Called for each failing condition. + */ virtual void onFail(std::string const&) { } - /// Called when a test logs output. + /** + * Called when a test logs output. + */ virtual void onLog(std::string const&) { diff --git a/include/xrpl/beast/unit_test/suite.h b/include/xrpl/beast/unit_test/suite.h index 487663fcc5..c20fe2522c 100644 --- a/include/xrpl/beast/unit_test/suite.h +++ b/include/xrpl/beast/unit_test/suite.h @@ -41,13 +41,14 @@ class Thread; enum class AbortT { NoAbortOnFail, AbortOnFail }; -/** A testsuite class. - - Derived classes execute a series of testcases, where each testcase is - a series of pass/fail tests. To provide a unit test using this class, - derive from it and use the BEAST_DEFINE_UNIT_TEST macro in a - translation unit. -*/ +/** + * A testsuite class. + * + * Derived classes execute a series of testcases, where each testcase is + * a series of pass/fail tests. To provide a unit test using this class, + * derive from it and use the BEAST_DEFINE_UNIT_TEST macro in a + * translation unit. + */ class Suite { private: @@ -118,16 +119,17 @@ private: { } - /** Open a new testcase. - - A testcase is a series of evaluated test conditions. A test - suite may have multiple test cases. A test is associated with - the last opened testcase. When the test first runs, a default - unnamed case is opened. Tests with only one case may omit the - call to testcase. - - @param abort Determines if suite continues running after a failure. - */ + /** + * Open a new testcase. + * + * A testcase is a series of evaluated test conditions. A test + * suite may have multiple test cases. A test is associated with + * the last opened testcase. When the test first runs, a default + * unnamed case is opened. Tests with only one case may omit the + * call to testcase. + * + * @param abort Determines if suite continues running after a failure. + */ void operator()(std::string const& name, AbortT abort = AbortT::NoAbortOnFail); @@ -140,19 +142,23 @@ private: }; public: - /** Logging output stream. - - Text sent to the log output stream will be forwarded to - the output stream associated with the runner. - */ + /** + * Logging output stream. + * + * Text sent to the log output stream will be forwarded to + * the output stream associated with the runner. + */ LogOs log; - /** Memberspace for declaring test cases. */ + /** + * Memberspace for declaring test cases. + */ TestcaseT testcase; - /** Returns the "current" running suite. - If no suite is running, nullptr is returned. - */ + /** + * Returns the "current" running suite. + * If no suite is running, nullptr is returned. + */ static Suite* thisSuite() { @@ -168,30 +174,34 @@ public: Suite& operator=(Suite const&) = delete; - /** Invokes the test using the specified runner. - - Data members are set up here instead of the constructor as a - convenience to writing the derived class to avoid repetition of - forwarded constructor arguments to the base. - Normally this is called by the framework for you. - */ + /** + * Invokes the test using the specified runner. + * + * Data members are set up here instead of the constructor as a + * convenience to writing the derived class to avoid repetition of + * forwarded constructor arguments to the base. + * Normally this is called by the framework for you. + */ template void operator()(Runner& r); - /** Record a successful test condition. */ + /** + * Record a successful test condition. + */ template void pass(); - /** Record a failure. - - @param reason Optional text added to the output on a failure. - - @param file The source code file where the test failed. - - @param line The source code line number where the test failed. - */ + /** + * Record a failure. + * + * @param reason Optional text added to the output on a failure. + * + * @param file The source code file where the test failed. + * + * @param line The source code line number where the test failed. + */ /** @{ */ template void @@ -202,23 +212,24 @@ public: fail(std::string const& reason = ""); /** @} */ - /** Evaluate a test condition. - - This function provides improved logging by incorporating the - file name and line number into the reported output on failure, - as well as additional text specified by the caller. - - @param shouldBeTrue The condition to test. The condition - is evaluated in a boolean context. - - @param reason Optional added text to output on a failure. - - @param file The source code file where the test failed. - - @param line The source code line number where the test failed. - - @return `true` if the test condition indicates success. - */ + /** + * Evaluate a test condition. + * + * This function provides improved logging by incorporating the + * file name and line number into the reported output on failure, + * as well as additional text specified by the caller. + * + * @param shouldBeTrue The condition to test. The condition + * is evaluated in a boolean context. + * + * @param reason Optional added text to output on a failure. + * + * @param file The source code file where the test failed. + * + * @param line The source code line number where the test failed. + * + * @return `true` if the test condition indicates success. + */ /** @{ */ template bool @@ -275,15 +286,19 @@ public: return unexcept(f, ""); } - /** Return the argument associated with the runner. */ + /** + * Return the argument associated with the runner. + */ std::string const& arg() const { return runner_->arg(); } - // DEPRECATED - // @return `true` if the test condition indicates success(a false value) + /** + * DEPRECATED + * @return `true` if the test condition indicates success(a false value) + */ template bool unexpected(Condition shouldBeFalse, String const& reason); @@ -305,7 +320,9 @@ private: return &kPTs; } - /** Runs the suite. */ + /** + * Runs the suite. + */ virtual void run() = 0; @@ -558,18 +575,20 @@ Suite::run(Runner& r) } #ifndef BEAST_EXPECT -/** Check a precondition. - - If the condition is false, the file and line number are reported. -*/ +/** + * Check a precondition. + * + * If the condition is false, the file and line number are reported. + */ #define BEAST_EXPECT(cond) expect(cond, __FILE__, __LINE__) #endif #ifndef BEAST_EXPECTS -/** Check a precondition. - - If the condition is false, the file and line number are reported. -*/ +/** + * Check a precondition. + * + * If the condition is false, the file and line number are reported. + */ #define BEAST_EXPECTS(cond, reason) \ ((cond) ? (pass(), true) : (fail((reason), __FILE__, __LINE__), false)) #endif @@ -593,41 +612,43 @@ Suite::run(Runner& r) // #ifndef BEAST_DEFINE_TESTSUITE -/** Enables insertion of test suites into the global container. - The default is to insert all test suite definitions into the global - container. If BEAST_DEFINE_TESTSUITE is user defined, this macro - has no effect. -*/ +/** + * Enables insertion of test suites into the global container. + * The default is to insert all test suite definitions into the global + * container. If BEAST_DEFINE_TESTSUITE is user defined, this macro + * has no effect. + */ #ifndef BEAST_NO_UNIT_TEST_INLINE #define BEAST_NO_UNIT_TEST_INLINE 0 #endif -/** Define a unit test suite. - - Class The type representing the class being tested. - Module Identifies the module. - Library Identifies the library. - - The declaration for the class implementing the test should be the same - as Class ## _test. For example, if Class is aged_ordered_container, the - test class must be declared as: - - @code - - struct aged_ordered_container_test : beast::unit_test::suite - { - //... - }; - - @endcode - - The macro invocation must appear in the same namespace as the test class. - - Unit test priorities were introduced so parallel unit_test::suites would - execute faster. Suites with longer running times have higher priorities - than unit tests with shorter running times. Suites with no priorities - are assumed to run most quickly, so they run last. -*/ +/** + * Define a unit test suite. + * + * Class The type representing the class being tested. + * Module Identifies the module. + * Library Identifies the library. + * + * The declaration for the class implementing the test should be the same + * as Class ## _test. For example, if Class is aged_ordered_container, the + * test class must be declared as: + * + * @code + * + * struct aged_ordered_container_test : beast::unit_test::suite + * { + * //... + * }; + * + * @endcode + * + * The macro invocation must appear in the same namespace as the test class. + * + * Unit test priorities were introduced so parallel unit_test::suites would + * execute faster. Suites with longer running times have higher priorities + * than unit tests with shorter running times. Suites with no priorities + * are assumed to run most quickly, so they run last. + */ #if BEAST_NO_UNIT_TEST_INLINE #define BEAST_DEFINE_TESTSUITE(Class, Module, Library) diff --git a/include/xrpl/beast/unit_test/suite_info.h b/include/xrpl/beast/unit_test/suite_info.h index bda10ae7e3..c4e3496f13 100644 --- a/include/xrpl/beast/unit_test/suite_info.h +++ b/include/xrpl/beast/unit_test/suite_info.h @@ -13,7 +13,9 @@ namespace beast::unit_test { class Runner; -/** Associates a unit test type with metadata. */ +/** + * Associates a unit test type with metadata. + */ class SuiteInfo { using run_type = std::function; @@ -60,21 +62,27 @@ public: return library_; } - /// Returns `true` if this suite only runs manually. + /** + * Returns `true` if this suite only runs manually. + */ [[nodiscard]] bool manual() const { return manual_; } - /// Return the canonical suite name as a string. + /** + * Return the canonical suite name as a string. + */ [[nodiscard]] std::string fullName() const { return library_ + "." + module_ + "." + name_; } - /// Run a new instance of the associated test suite. + /** + * Run a new instance of the associated test suite. + */ void run(Runner& r) const { @@ -93,7 +101,9 @@ public: //------------------------------------------------------------------------------ -/// Convenience for producing SuiteInfo for a given test type. +/** + * Convenience for producing SuiteInfo for a given test type. + */ template SuiteInfo makeSuiteInfo(std::string name, std::string module, std::string library, bool manual, int priority) diff --git a/include/xrpl/beast/unit_test/suite_list.h b/include/xrpl/beast/unit_test/suite_list.h index 7dd0dd80f0..057a362859 100644 --- a/include/xrpl/beast/unit_test/suite_list.h +++ b/include/xrpl/beast/unit_test/suite_list.h @@ -16,7 +16,9 @@ namespace beast::unit_test { -/// A container of test suites. +/** + * A container of test suites. + */ class SuiteList : public detail::ConstContainer> { private: @@ -26,10 +28,11 @@ private: #endif public: - /** Insert a suite into the set. - - The suite must not already exist. - */ + /** + * Insert a suite into the set. + * + * The suite must not already exist. + */ template void insert(char const* name, char const* module, char const* library, bool manual, int priority); diff --git a/include/xrpl/beast/unit_test/thread.h b/include/xrpl/beast/unit_test/thread.h index 91d8cf3cab..5a5a99d149 100644 --- a/include/xrpl/beast/unit_test/thread.h +++ b/include/xrpl/beast/unit_test/thread.h @@ -14,7 +14,9 @@ namespace beast::unit_test { -/** Replacement for std::thread that handles exceptions in unit tests. */ +/** + * Replacement for std::thread that handles exceptions in unit tests. + */ class Thread { private: diff --git a/include/xrpl/beast/utility/Journal.h b/include/xrpl/beast/utility/Journal.h index 3de3cfb0e0..9f0a1ead66 100644 --- a/include/xrpl/beast/utility/Journal.h +++ b/include/xrpl/beast/utility/Journal.h @@ -10,7 +10,9 @@ namespace beast { -/** Severity level / threshold of a Journal message. */ +/** + * Severity level / threshold of a Journal message. + */ enum class Severity : std::uint8_t { All = 0, @@ -25,18 +27,19 @@ enum class Severity : std::uint8_t { None = Disabled }; -/** A generic endpoint for log messages. - - The Journal has a few simple goals: - - * To be light-weight and copied by value. - * To allow logging statements to be left in source code. - * The logging is controlled at run-time based on a logging threshold. - - It is advisable to check Journal::active(level) prior to formatting log - text. Doing so sidesteps expensive text formatting when the results - will not be sent to the log. -*/ +/** + * A generic endpoint for log messages. + * + * The Journal has a few simple goals: + * + * * To be light-weight and copied by value. + * * To allow logging statements to be left in source code. + * * The logging is controlled at run-time based on a logging threshold. + * + * It is advisable to check Journal::active(level) prior to formatting log + * text. Doing so sidesteps expensive text formatting when the results + * will not be sent to the log. + */ class Journal { public: @@ -49,7 +52,9 @@ private: public: //-------------------------------------------------------------------------- - /** Abstraction for the underlying message destination. */ + /** + * Abstraction for the underlying message destination. + */ class Sink { protected: @@ -63,36 +68,47 @@ public: Sink& operator=(Sink const& lhs) = delete; - /** Returns `true` if text at the passed severity produces output. */ + /** + * Returns `true` if text at the passed severity produces output. + */ [[nodiscard]] virtual bool active(Severity level) const; - /** Returns `true` if a message is also written to the Output Window - * (MSVC). */ + /** + * Returns `true` if a message is also written to the Output Window + * (MSVC). + */ [[nodiscard]] virtual bool console() const; - /** Set whether messages are also written to the Output Window (MSVC). + /** + * Set whether messages are also written to the Output Window (MSVC). */ virtual void console(bool output); - /** Returns the minimum severity level this sink will report. */ + /** + * Returns the minimum severity level this sink will report. + */ [[nodiscard]] virtual Severity threshold() const; - /** Set the minimum severity this sink will report. */ + /** + * Set the minimum severity this sink will report. + */ virtual void threshold(Severity thresh); - /** Write text to the sink at the specified severity. - A conforming implementation will not write the text if the passed - level is below the current threshold(). - */ + /** + * Write text to the sink at the specified severity. + * A conforming implementation will not write the text if the passed + * level is below the current threshold(). + */ virtual void write(Severity level, std::string const& text) = 0; - /** Bypass filter and write text to the sink at the specified severity. + /** + * Bypass filter and write text to the sink at the specified severity. * Always write the message, but maintain the same formatting as if * it passed through a level filter. * @@ -116,7 +132,9 @@ public: static_assert(std::is_nothrow_destructible_v); #endif - /** Returns a Sink which does nothing. */ + /** + * Returns a Sink which does nothing. + */ static Sink& getNullSink(); @@ -174,26 +192,33 @@ public: //-------------------------------------------------------------------------- public: - /** Provide a light-weight way to check active() before string formatting */ + /** + * Provide a light-weight way to check active() before string formatting + */ class Stream { public: - /** Create a stream which produces no output. */ + /** + * Create a stream which produces no output. + */ explicit Stream() : sink_(getNullSink()), level_(Severity::Disabled) { } - /** Create a stream that writes at the given level. - - Constructor is inlined so checking active() very inexpensive. - */ + /** + * Create a stream that writes at the given level. + * + * Constructor is inlined so checking active() very inexpensive. + */ Stream(Sink& sink, Severity level) : sink_(sink), level_(level) { XRPL_ASSERT( level_ < Severity::Disabled, "beast::Journal::Stream::Stream : maximum level"); } - /** Construct or copy another Stream. */ + /** + * Construct or copy another Stream. + */ Stream(Stream const& other) : Stream(other.sink_, other.level_) { } @@ -201,21 +226,27 @@ public: Stream& operator=(Stream const& other) = delete; - /** Returns the Sink that this Stream writes to. */ + /** + * Returns the Sink that this Stream writes to. + */ [[nodiscard]] Sink& sink() const { return sink_; } - /** Returns the Severity level of messages this Stream reports. */ + /** + * Returns the Severity level of messages this Stream reports. + */ [[nodiscard]] Severity level() const { return level_; } - /** Returns `true` if sink logs anything at this stream's level. */ + /** + * Returns `true` if sink logs anything at this stream's level. + */ /** @{ */ [[nodiscard]] bool active() const @@ -230,7 +261,9 @@ public: } /** @} */ - /** Output stream support. */ + /** + * Output stream support. + */ /** @{ */ ScopedStream operator<<(std::ostream& manip(std::ostream&)) const; @@ -256,39 +289,50 @@ public: //-------------------------------------------------------------------------- - /** Journal has no default constructor. */ + /** + * Journal has no default constructor. + */ Journal() = delete; - /** Create a journal that writes to the specified sink. */ + /** + * Create a journal that writes to the specified sink. + */ explicit Journal(Sink& sink) : sink_(&sink) { } - /** Returns the Sink associated with this Journal. */ + /** + * Returns the Sink associated with this Journal. + */ [[nodiscard]] Sink& sink() const { return *sink_; } - /** Returns a stream for this sink, with the specified severity level. */ + /** + * Returns a stream for this sink, with the specified severity level. + */ [[nodiscard]] Stream stream(Severity level) const { return Stream(*sink_, level); } - /** Returns `true` if any message would be logged at this severity level. - For a message to be logged, the severity must be at or above the - sink's severity threshold. - */ + /** + * Returns `true` if any message would be logged at this severity level. + * For a message to be logged, the severity must be at or above the + * sink's severity threshold. + */ [[nodiscard]] bool active(Severity level) const { return sink_->active(level); } - /** Severity stream access functions. */ + /** + * Severity stream access functions. + */ /** @{ */ [[nodiscard]] Stream trace() const diff --git a/include/xrpl/beast/utility/PropertyStream.h b/include/xrpl/beast/utility/PropertyStream.h index 3fb6df53d9..f32f5b7fef 100644 --- a/include/xrpl/beast/utility/PropertyStream.h +++ b/include/xrpl/beast/utility/PropertyStream.h @@ -12,7 +12,9 @@ namespace beast { //------------------------------------------------------------------------------ -/** Abstract stream with RAII containers that produce a property tree. */ +/** + * Abstract stream with RAII containers that produce a property tree. + */ class PropertyStream { public: @@ -306,7 +308,9 @@ public: // //------------------------------------------------------------------------------ -/** Subclasses can be called to write to a stream and have children. */ +/** + * Subclasses can be called to write to a stream and have children. + */ class PropertyStream::Source { private: @@ -324,17 +328,22 @@ public: Source& operator=(Source const&) = delete; - /** Returns the name of this source. */ + /** + * Returns the name of this source. + */ [[nodiscard]] std::string const& name() const; - /** Add a child source. */ + /** + * Add a child source. + */ void add(Source& source); - /** Add a child source by pointer. - The source pointer is returned so it can be used in ctor-initializers. - */ + /** + * Add a child source by pointer. + * The source pointer is returned so it can be used in ctor-initializers. + */ template Derived* add(Derived* child) @@ -343,45 +352,55 @@ public: return child; } - /** Remove a child source from this Source. */ + /** + * Remove a child source from this Source. + */ void remove(Source& child); - /** Remove all child sources from this Source. */ + /** + * Remove all child sources from this Source. + */ void removeAll(); - /** Write only this Source to the stream. */ + /** + * Write only this Source to the stream. + */ void writeOne(PropertyStream& stream); - /** write this source and all its children recursively to the stream. */ + /** + * write this source and all its children recursively to the stream. + */ void write(PropertyStream& stream); - /** Parse the path and write the corresponding Source and optional children. - If the source is found, it is written. If the wildcard character '*' - exists as the last character in the path, then all the children are - written recursively. - */ + /** + * Parse the path and write the corresponding Source and optional children. + * If the source is found, it is written. If the wildcard character '*' + * exists as the last character in the path, then all the children are + * written recursively. + */ void write(PropertyStream& stream, std::string const& path); - /** Parse the dot-delimited Source path and return the result. - The first value will be a pointer to the Source object corresponding - to the given path. If no Source object exists, then the first value - will be nullptr and the second value will be undefined. - The second value is a boolean indicating whether or not the path string - specifies the wildcard character '*' as the last character. - - print statement examples - "parent.child" prints child and all of its children - "parent.child." start at the parent and print down to child - "parent.grandchild" prints nothing- grandchild not direct descendent - "parent.grandchild." starts at the parent and prints down to grandchild - "parent.grandchild.*" starts at parent, print through grandchild - children - */ + /** + * Parse the dot-delimited Source path and return the result. + * The first value will be a pointer to the Source object corresponding + * to the given path. If no Source object exists, then the first value + * will be nullptr and the second value will be undefined. + * The second value is a boolean indicating whether or not the path string + * specifies the wildcard character '*' as the last character. + * + * print statement examples + * "parent.child" prints child and all of its children + * "parent.child." start at the parent and print down to child + * "parent.grandchild" prints nothing- grandchild not direct descendent + * "parent.grandchild." starts at the parent and prints down to grandchild + * "parent.grandchild.*" starts at parent, print through grandchild + * children + */ std::pair find(std::string path); @@ -401,9 +420,10 @@ public: //-------------------------------------------------------------------------- - /** Subclass override. - The default version does nothing. - */ + /** + * Subclass override. + * The default version does nothing. + */ virtual void onWrite(Map&); }; diff --git a/include/xrpl/beast/utility/WrappedSink.h b/include/xrpl/beast/utility/WrappedSink.h index a24ad595db..3ab48e1939 100644 --- a/include/xrpl/beast/utility/WrappedSink.h +++ b/include/xrpl/beast/utility/WrappedSink.h @@ -7,7 +7,9 @@ namespace beast { -/** Wraps a Journal::Sink to prefix its output with a string. */ +/** + * Wraps a Journal::Sink to prefix its output with a string. + */ // A WrappedSink both is a Sink and has a Sink: // o It inherits from Sink so it has the correct interface. diff --git a/include/xrpl/beast/utility/Zero.h b/include/xrpl/beast/utility/Zero.h index e28589760b..406921c500 100644 --- a/include/xrpl/beast/utility/Zero.h +++ b/include/xrpl/beast/utility/Zero.h @@ -4,22 +4,23 @@ namespace beast { -/** Zero allows classes to offer efficient comparisons to zero. - - Zero is a struct to allow classes to efficiently compare with zero without - requiring an rvalue construction. - - It's often the case that we have classes which combine a number and a unit. - In such cases, comparisons like t > 0 or t != 0 make sense, but comparisons - like t > 1 or t != 1 do not. - - The class Zero allows such comparisons to be easily made. - - The comparing class T either needs to have a method called signum() which - returns a positive number, 0, or a negative; or there needs to be a signum - function which resolves in the namespace which takes an instance of T and - returns a positive, zero or negative number. -*/ +/** + * Zero allows classes to offer efficient comparisons to zero. + * + * Zero is a struct to allow classes to efficiently compare with zero without + * requiring an rvalue construction. + * + * It's often the case that we have classes which combine a number and a unit. + * In such cases, comparisons like t > 0 or t != 0 make sense, but comparisons + * like t > 1 or t != 1 do not. + * + * The class Zero allows such comparisons to be easily made. + * + * The comparing class T either needs to have a method called signum() which + * returns a positive number, 0, or a negative; or there needs to be a signum + * function which resolves in the namespace which takes an instance of T and + * returns a positive, zero or negative number. + */ struct Zero { @@ -28,7 +29,9 @@ struct Zero inline constexpr Zero kZero{}; -/** Default implementation of signum calls the method on the class. */ +/** + * Default implementation of signum calls the method on the class. + */ template auto signum(T const& t) diff --git a/include/xrpl/beast/utility/maybe_const.h b/include/xrpl/beast/utility/maybe_const.h index 10b2eaf7f6..848ea86cb2 100644 --- a/include/xrpl/beast/utility/maybe_const.h +++ b/include/xrpl/beast/utility/maybe_const.h @@ -4,7 +4,9 @@ namespace beast { -/** Makes T const or non const depending on a bool. */ +/** + * Makes T const or non const depending on a bool. + */ template struct MaybeConst { @@ -13,7 +15,9 @@ struct MaybeConst conditional_t::type const, std::remove_const_t>; }; -/** Alias for omitting `typename`. */ +/** + * Alias for omitting `typename`. + */ template using maybe_const_t = MaybeConst::type; diff --git a/include/xrpl/beast/utility/temp_dir.h b/include/xrpl/beast/utility/temp_dir.h index ec661b51c4..a0ff1e6940 100644 --- a/include/xrpl/beast/utility/temp_dir.h +++ b/include/xrpl/beast/utility/temp_dir.h @@ -6,11 +6,12 @@ namespace beast { -/** RAII temporary directory. - - The directory and all its contents are deleted when - the instance of `temp_dir` is destroyed. -*/ +/** + * RAII temporary directory. + * + * The directory and all its contents are deleted when + * the instance of `temp_dir` is destroyed. + */ class TempDir { boost::filesystem::path path_; @@ -22,7 +23,9 @@ public: operator=(TempDir const&) = delete; #endif - /// Construct a temporary directory. + /** + * Construct a temporary directory. + */ TempDir() { auto const dir = boost::filesystem::temp_directory_path(); @@ -33,7 +36,9 @@ public: boost::filesystem::create_directory(path_); } - /// Destroy a temporary directory. + /** + * Destroy a temporary directory. + */ ~TempDir() { // use non-throwing calls in the destructor @@ -42,17 +47,20 @@ public: // TODO: warn/notify if ec set ? } - /// Get the native path for the temporary directory + /** + * Get the native path for the temporary directory + */ [[nodiscard]] std::string path() const { return path_.string(); } - /** Get the native path for the a file. - - The file does not need to exist. - */ + /** + * Get the native path for the a file. + * + * The file does not need to exist. + */ [[nodiscard]] std::string file(std::string const& name) const { diff --git a/include/xrpl/beast/xor_shift_engine.h b/include/xrpl/beast/xor_shift_engine.h index 45baecf101..6a7272c195 100644 --- a/include/xrpl/beast/xor_shift_engine.h +++ b/include/xrpl/beast/xor_shift_engine.h @@ -85,14 +85,15 @@ XorShiftEngine::murmurhash3(result_type x) -> result_type } // namespace detail -/** XOR-shift Generator. - - Meets the requirements of UniformRandomNumberGenerator. - - Simple and fast RNG based on: - http://xorshift.di.unimi.it/xorshift128plus.c - does not accept seed==0 -*/ +/** + * XOR-shift Generator. + * + * Meets the requirements of UniformRandomNumberGenerator. + * + * Simple and fast RNG based on: + * http://xorshift.di.unimi.it/xorshift128plus.c + * does not accept seed==0 + */ using xor_shift_engine = detail::XorShiftEngine<>; } // namespace beast diff --git a/include/xrpl/conditions/Condition.h b/include/xrpl/conditions/Condition.h index 3c798663d7..365a41a087 100644 --- a/include/xrpl/conditions/Condition.h +++ b/include/xrpl/conditions/Condition.h @@ -24,42 +24,49 @@ enum class Type : std::uint8_t { class Condition { public: - /** The largest binary condition we support. - - @note This value will be increased in the future, but it - must never decrease, as that could cause conditions - that were previously considered valid to no longer - be allowed. - */ + /** + * The largest binary condition we support. + * + * @note This value will be increased in the future, but it + * must never decrease, as that could cause conditions + * that were previously considered valid to no longer + * be allowed. + */ static constexpr std::size_t kMaxSerializedCondition = 128; - /** Load a condition from its binary form - - @param s The buffer containing the fulfillment to load. - @param ec Set to the error, if any occurred. - - The binary format for a condition is specified in the - cryptoconditions RFC. See: - - https://tools.ietf.org/html/draft-thomas-crypto-conditions-02#section-7.2 - */ + /** + * Load a condition from its binary form + * + * @param s The buffer containing the fulfillment to load. + * @param ec Set to the error, if any occurred. + * + * The binary format for a condition is specified in the + * cryptoconditions RFC. See: + * + * https://tools.ietf.org/html/draft-thomas-crypto-conditions-02#section-7.2 + */ static std::unique_ptr deserialize(Slice s, std::error_code& ec); public: Type type; - /** An identifier for this condition. - - This fingerprint is meant to be unique only with - respect to other conditions of the same type. - */ + /** + * An identifier for this condition. + * + * This fingerprint is meant to be unique only with + * respect to other conditions of the same type. + */ Buffer fingerprint; - /** The cost associated with this condition. */ + /** + * The cost associated with this condition. + */ std::uint32_t cost; - /** For compound conditions, set of conditions includes */ + /** + * For compound conditions, set of conditions includes + */ std::set subtypes; Condition(Type t, std::uint32_t c, Slice fp) : type(t), fingerprint(fp), cost(c) diff --git a/include/xrpl/conditions/Fulfillment.h b/include/xrpl/conditions/Fulfillment.h index a3001b2620..11f3165a58 100644 --- a/include/xrpl/conditions/Fulfillment.h +++ b/include/xrpl/conditions/Fulfillment.h @@ -14,64 +14,73 @@ namespace xrpl::cryptoconditions { struct Fulfillment { public: - /** The largest binary fulfillment we support. - - @note This value will be increased in the future, but it - must never decrease, as that could cause fulfillments - that were previously considered valid to no longer - be allowed. - */ + /** + * The largest binary fulfillment we support. + * + * @note This value will be increased in the future, but it + * must never decrease, as that could cause fulfillments + * that were previously considered valid to no longer + * be allowed. + */ static constexpr std::size_t kMaxSerializedFulfillment = 256; - /** Load a fulfillment from its binary form - - @param s The buffer containing the fulfillment to load. - @param ec Set to the error, if any occurred. - - The binary format for a fulfillment is specified in the - cryptoconditions RFC. See: - - https://tools.ietf.org/html/draft-thomas-crypto-conditions-02#section-7.3 - */ + /** + * Load a fulfillment from its binary form + * + * @param s The buffer containing the fulfillment to load. + * @param ec Set to the error, if any occurred. + * + * The binary format for a fulfillment is specified in the + * cryptoconditions RFC. See: + * + * https://tools.ietf.org/html/draft-thomas-crypto-conditions-02#section-7.3 + */ static std::unique_ptr deserialize(Slice s, std::error_code& ec); public: virtual ~Fulfillment() = default; - /** Returns the fulfillment's fingerprint: - - The fingerprint is an octet string uniquely - representing this fulfillment's condition - with respect to other conditions of the - same type. - */ + /** + * Returns the fulfillment's fingerprint: + * + * The fingerprint is an octet string uniquely + * representing this fulfillment's condition + * with respect to other conditions of the + * same type. + */ [[nodiscard]] virtual Buffer fingerprint() const = 0; - /** Returns the type of this condition. */ + /** + * Returns the type of this condition. + */ [[nodiscard]] virtual Type type() const = 0; - /** Validates a fulfillment. */ + /** + * Validates a fulfillment. + */ [[nodiscard]] virtual bool validate(Slice data) const = 0; - /** Calculates the cost associated with this fulfillment. * - - The cost function is deterministic and depends on the - type and properties of the condition and the fulfillment - that the condition is generated from. - */ + /** + * Calculates the cost associated with this fulfillment. * + * + * The cost function is deterministic and depends on the + * type and properties of the condition and the fulfillment + * that the condition is generated from. + */ [[nodiscard]] virtual std::uint32_t cost() const = 0; - /** Returns the condition associated with the given fulfillment. - - This process is completely deterministic. All implementations - will, if compliant, produce the identical condition for the - same fulfillment. - */ + /** + * Returns the condition associated with the given fulfillment. + * + * This process is completely deterministic. All implementations + * will, if compliant, produce the identical condition for the + * same fulfillment. + */ [[nodiscard]] virtual Condition condition() const = 0; }; @@ -90,36 +99,40 @@ operator!=(Fulfillment const& lhs, Fulfillment const& rhs) return !(lhs == rhs); } -/** Determine whether the given fulfillment and condition match */ +/** + * Determine whether the given fulfillment and condition match + */ bool match(Fulfillment const& f, Condition const& c); -/** Verify if the given message satisfies the fulfillment. - - @param f The fulfillment - @param c The condition - @param m The message - - @note the message is not relevant for some conditions - and a fulfillment will successfully satisfy its - condition for any given message. -*/ +/** + * Verify if the given message satisfies the fulfillment. + * + * @param f The fulfillment + * @param c The condition + * @param m The message + * + * @note the message is not relevant for some conditions + * and a fulfillment will successfully satisfy its + * condition for any given message. + */ bool validate(Fulfillment const& f, Condition const& c, Slice m); -/** Verify a cryptoconditional trigger. - - A cryptoconditional trigger is a cryptocondition with - an empty message. - - When using such triggers, it is recommended that the - trigger be of type preimage, prefix or threshold. If - a signature type is used (i.e. Ed25519 or RSA-SHA256) - then the Ed25519 or RSA keys should be single-use keys. - - @param f The fulfillment - @param c The condition -*/ +/** + * Verify a cryptoconditional trigger. + * + * A cryptoconditional trigger is a cryptocondition with + * an empty message. + * + * When using such triggers, it is recommended that the + * trigger be of type preimage, prefix or threshold. If + * a signature type is used (i.e. Ed25519 or RSA-SHA256) + * then the Ed25519 or RSA keys should be single-use keys. + * + * @param f The fulfillment + * @param c The condition + */ bool validate(Fulfillment const& f, Condition const& c); diff --git a/include/xrpl/conditions/detail/PreimageSha256.h b/include/xrpl/conditions/detail/PreimageSha256.h index 0973a52e4a..007588a0b5 100644 --- a/include/xrpl/conditions/detail/PreimageSha256.h +++ b/include/xrpl/conditions/detail/PreimageSha256.h @@ -18,23 +18,25 @@ namespace xrpl::cryptoconditions { class PreimageSha256 final : public Fulfillment { public: - /** The maximum allowed length of a preimage. - - The specification does not specify a minimum supported - length, nor does it require all conditions to support - the same minimum length. - - While future versions of this code will never lower - this limit, they may opt to raise it. - */ + /** + * The maximum allowed length of a preimage. + * + * The specification does not specify a minimum supported + * length, nor does it require all conditions to support + * the same minimum length. + * + * While future versions of this code will never lower + * this limit, they may opt to raise it. + */ static constexpr std::size_t kMaxPreimageLength = 128; - /** Parse the payload for a PreimageSha256 condition - - @param s A slice containing the DER encoded payload - @param ec indicates success or failure of the operation - @return the preimage, if successful; empty pointer otherwise. - */ + /** + * Parse the payload for a PreimageSha256 condition + * + * @param s A slice containing the DER encoded payload + * @param ec indicates success or failure of the operation + * @return the preimage, if successful; empty pointer otherwise. + */ static std::unique_ptr deserialize(Slice s, std::error_code& ec) { diff --git a/include/xrpl/config/BasicConfig.h b/include/xrpl/config/BasicConfig.h index 5680b51fe7..607a0c3e5f 100644 --- a/include/xrpl/config/BasicConfig.h +++ b/include/xrpl/config/BasicConfig.h @@ -21,9 +21,10 @@ using IniFileSections = std::unordered_map //------------------------------------------------------------------------------ -/** Holds a collection of configuration values. - A configuration file contains zero or more sections. -*/ +/** + * Holds a collection of configuration values. + * A configuration file contains zero or more sections. + */ class Section { private: @@ -36,28 +37,34 @@ private: using const_iterator = decltype(lookup_)::const_iterator; public: - /** Create an empty section. */ + /** + * Create an empty section. + */ explicit Section(std::string name = ""); - /** Returns the name of this section. */ + /** + * Returns the name of this section. + */ [[nodiscard]] std::string const& name() const { return name_; } - /** Returns all the lines in the section. - This includes everything. - */ + /** + * Returns all the lines in the section. + * This includes everything. + */ [[nodiscard]] std::vector const& lines() const { return lines_; } - /** Returns all the values in the section. - Values are non-empty lines which are not key/value pairs. - */ + /** + * Returns all the values in the section. + * Values are non-empty lines which are not key/value pairs. + */ [[nodiscard]] std::vector const& values() const { @@ -84,7 +91,7 @@ public: * Get the legacy value for this section. * * @return The retrieved value. A section with an empty legacy value returns - an empty string. + * an empty string. */ [[nodiscard]] std::string legacy() const @@ -99,28 +106,34 @@ public: return lines_[0]; } - /** Set a key/value pair. - The previous value is discarded. - */ + /** + * Set a key/value pair. + * The previous value is discarded. + */ void set(std::string const& key, std::string const& value); - /** Append a set of lines to this section. - Lines containing key/value pairs are added to the map, - else they are added to the values list. Everything is - added to the lines list. - */ + /** + * Append a set of lines to this section. + * Lines containing key/value pairs are added to the map, + * else they are added to the values list. Everything is + * added to the lines list. + */ void append(std::vector const& lines); - /** Append a line to this section. */ + /** + * Append a line to this section. + */ void append(std::string const& line) { append(std::vector{line}); } - /** Returns `true` if a key with the given name exists. */ + /** + * Returns `true` if a key with the given name exists. + */ [[nodiscard]] bool exists(std::string const& name) const; @@ -134,7 +147,9 @@ public: return boost::lexical_cast(iter->second); } - /// Returns a value if present, else another value. + /** + * Returns a value if present, else another value. + */ template [[nodiscard]] T valueOr(std::string const& name, T const& other) const @@ -199,23 +214,27 @@ public: //------------------------------------------------------------------------------ -/** Holds unparsed configuration information. - The raw data sections are processed with intermediate parsers specific - to each module instead of being all parsed in a central location. -*/ +/** + * Holds unparsed configuration information. + * The raw data sections are processed with intermediate parsers specific + * to each module instead of being all parsed in a central location. + */ class BasicConfig { private: std::unordered_map map_; public: - /** Returns `true` if a section with the given name exists. */ + /** + * Returns `true` if a section with the given name exists. + */ [[nodiscard]] bool exists(std::string const& name) const; - /** Returns the section with the given name. - If the section does not exist, an empty section is returned. - */ + /** + * Returns the section with the given name. + * If the section does not exist, an empty section is returned. + */ /** @{ */ Section& section(std::string const& name); @@ -236,37 +255,39 @@ public: } /** @} */ - /** Overwrite a key/value pair with a command line argument - If the section does not exist it is created. - The previous value, if any, is overwritten. - */ + /** + * Overwrite a key/value pair with a command line argument + * If the section does not exist it is created. + * The previous value, if any, is overwritten. + */ void overwrite(std::string const& section, std::string const& key, std::string const& value); - /** Remove all the key/value pairs from the section. + /** + * Remove all the key/value pairs from the section. */ void deprecatedClearSection(std::string const& section); /** - * Set a value that is not a key/value pair. + * Set a value that is not a key/value pair. * - * The value is stored as the section's first value and may be retrieved - * through section::legacy. + * The value is stored as the section's first value and may be retrieved + * through section::legacy. * - * @param section Name of the section to modify. - * @param value Contents of the legacy value. + * @param section Name of the section to modify. + * @param value Contents of the legacy value. */ void legacy(std::string const& section, std::string value); /** - * Get the legacy value of a section. A section with a - * single-line value may be retrieved as a legacy value. + * Get the legacy value of a section. A section with a + * single-line value may be retrieved as a legacy value. * - * @param sectionName Retrieve the contents of this section's - * legacy value. - * @return Contents of the legacy value. + * @param sectionName Retrieve the contents of this section's + * legacy value. + * @return Contents of the legacy value. */ [[nodiscard]] std::string legacy(std::string const& sectionName) const; @@ -289,11 +310,12 @@ protected: //------------------------------------------------------------------------------ -/** Set a value from a configuration Section - If the named value is not found or doesn't parse as a T, - the variable is unchanged. - @return `true` if value was set. -*/ +/** + * Set a value from a configuration Section + * If the named value is not found or doesn't parse as a T, + * the variable is unchanged. + * @return `true` if value was set. + */ template bool set(T& target, std::string const& name, Section const& section) @@ -312,11 +334,12 @@ set(T& target, std::string const& name, Section const& section) return foundAndValid; } -/** Set a value from a configuration Section - If the named value is not found or doesn't cast to T, - the variable is assigned the default. - @return `true` if the named value was found and is valid. -*/ +/** + * Set a value from a configuration Section + * If the named value is not found or doesn't cast to T, + * the variable is assigned the default. + * @return `true` if the named value was found and is valid. + */ template bool set(T& target, T const& defaultValue, std::string const& name, Section const& section) @@ -327,10 +350,11 @@ set(T& target, T const& defaultValue, std::string const& name, Section const& se return foundAndValid; } -/** Retrieve a key/value pair from a section. - @return The value string converted to T if it exists - and can be parsed, or else defaultValue. -*/ +/** + * Retrieve a key/value pair from a section. + * @return The value string converted to T if it exists + * and can be parsed, or else defaultValue. + */ // NOTE This routine might be more clumsy than the previous two template T diff --git a/include/xrpl/core/ClosureCounter.h b/include/xrpl/core/ClosureCounter.h index ed15db032e..33899d671b 100644 --- a/include/xrpl/core/ClosureCounter.h +++ b/include/xrpl/core/ClosureCounter.h @@ -30,8 +30,8 @@ namespace xrpl { * the caller that they should drop the closure and cancel their operation. * `join` blocks until all existing closure substitutes are destroyed. * - * \tparam Ret The return type of the closure. - * \tparam Args The argument types of the closure. + * @tparam Ret The return type of the closure. + * @tparam Args The argument types of the closure. */ template class ClosureCounter @@ -131,18 +131,21 @@ public: ClosureCounter& operator=(ClosureCounter const&) = delete; - /** Destructor verifies all in-flight closures are complete. */ + /** + * Destructor verifies all in-flight closures are complete. + */ ~ClosureCounter() { using namespace std::chrono_literals; join("ClosureCounter", 1s, debugLog()); } - /** Returns once all counted in-flight closures are destroyed. - - @param name Name reported if join time exceeds wait. - @param wait If join() exceeds this duration report to Journal. - @param j Journal written to if wait is exceeded. + /** + * Returns once all counted in-flight closures are destroyed. + * + * @param name Name reported if join time exceeds wait. + * @param wait If join() exceeds this duration report to Journal. + * @param j Journal written to if wait is exceeded. */ void join(char const* name, std::chrono::milliseconds wait, beast::Journal j) @@ -160,13 +163,14 @@ public: } } - /** Wrap the passed closure with a reference counter. - - @param closure Closure that accepts Args parameters and returns Ret. - @return If join() has been called returns std::nullopt. Otherwise - returns a std::optional that wraps closure with a - reference counter. - */ + /** + * Wrap the passed closure with a reference counter. + * + * @param closure Closure that accepts Args parameters and returns Ret. + * @return If join() has been called returns std::nullopt. Otherwise + * returns a std::optional that wraps closure with a + * reference counter. + */ template std::optional> wrap(Closure&& closure) @@ -180,19 +184,22 @@ public: return ret; } - /** Current number of Closures outstanding. Only useful for testing. */ + /** + * Current number of Closures outstanding. Only useful for testing. + */ int count() const { return closureCount_; } - /** Returns true if this has been joined. - - Even if true is returned, counted closures may still be in flight. - However if (joined() && (count() == 0)) there should be no more - counted closures in flight. - */ + /** + * Returns true if this has been joined. + * + * Even if true is returned, counted closures may still be in flight. + * However if (joined() && (count() == 0)) there should be no more + * counted closures in flight. + */ bool joined() const { diff --git a/include/xrpl/core/Coro.ipp b/include/xrpl/core/Coro.ipp index 133caf37a9..9a45dac504 100644 --- a/include/xrpl/core/Coro.ipp +++ b/include/xrpl/core/Coro.ipp @@ -4,8 +4,10 @@ namespace xrpl { -/// Coroutine stack size (1.5 MB). Increased from 1 MB because -/// ASAN-instrumented deep call stacks exceeded the original limit. +/** + * Coroutine stack size (1.5 MB). Increased from 1 MB because + * ASAN-instrumented deep call stacks exceeded the original limit. + */ constexpr std::size_t kCoroStackSize = 1536 * 1024; template diff --git a/include/xrpl/core/HashRouter.h b/include/xrpl/core/HashRouter.h index dad1afb405..20aafecc5f 100644 --- a/include/xrpl/core/HashRouter.h +++ b/include/xrpl/core/HashRouter.h @@ -75,19 +75,21 @@ any(HashRouterFlags flags) class Config; -/** Routing table for objects identified by hash. - - This table keeps track of which hashes have been received by which peers. - It is used to manage the routing and broadcasting of messages in the peer - to peer overlay. -*/ +/** + * Routing table for objects identified by hash. + * + * This table keeps track of which hashes have been received by which peers. + * It is used to manage the routing and broadcasting of messages in the peer + * to peer overlay. + */ class HashRouter { public: // The type here *MUST* match the type of Peer::id_t using PeerShortID = std::uint32_t; - /** Structure used to customize @ref HashRouter behavior. + /** + * Structure used to customize @ref HashRouter behavior. * * Even though these items are configurable, they are undocumented. Don't * change them unless there is a good reason, and network-wide coordination @@ -97,22 +99,27 @@ public: */ struct Setup { - /// Default constructor + /** + * Default constructor + */ explicit Setup() = default; using seconds = std::chrono::seconds; - /** Expiration time for a hash entry + /** + * Expiration time for a hash entry */ seconds holdTime{300}; - /** Amount of time required before a relayed item will be relayed again. + /** + * Amount of time required before a relayed item will be relayed again. */ seconds relayTime{30}; }; private: - /** An entry in the routing table. + /** + * An entry in the routing table. */ class Entry : public CountedObject { @@ -138,26 +145,31 @@ private: flags_ |= flagsToSet; } - /** Return set of peers we've relayed to and reset tracking */ + /** + * Return set of peers we've relayed to and reset tracking + */ std::set releasePeerSet() { return std::move(peers_); } - /** Return seated relay time point if the message has been relayed */ + /** + * Return seated relay time point if the message has been relayed + */ [[nodiscard]] std::optional relayed() const { return relayed_; } - /** Determines if this item should be relayed. - - Checks whether the item has been recently relayed. - If it has, return false. If it has not, update the - last relay timestamp and return true. - */ + /** + * Determines if this item should be relayed. + * + * Checks whether the item has been recently relayed. + * If it has, return false. If it has not, update the + * last relay timestamp and return true. + */ bool shouldRelay(Stopwatch::time_point const& now, std::chrono::seconds relayTime) { @@ -203,11 +215,13 @@ public: bool addSuppressionPeer(uint256 const& key, PeerShortID peer); - /** Add a suppression peer and get message's relay status. + /** + * Add a suppression peer and get message's relay status. * Return pair: * element 1: true if the peer is added. * element 2: optional is seated to the relay time point or - * is unseated if has not relayed yet. */ + * is unseated if has not relayed yet. + */ std::pair> addSuppressionPeerWithStatus(uint256 const& key, PeerShortID peer); @@ -222,28 +236,30 @@ public: HashRouterFlags& flags, std::chrono::seconds txInterval); - /** Set the flags on a hash. - - @return `true` if the flags were changed. `false` if unchanged. - */ + /** + * Set the flags on a hash. + * + * @return `true` if the flags were changed. `false` if unchanged. + */ bool setFlags(uint256 const& key, HashRouterFlags flags); HashRouterFlags getFlags(uint256 const& key); - /** Determines whether the hashed item should be relayed. - - Effects: - - If the item should be relayed, this function will not - return a seated optional again until the relay time has expired. - The internal set of peers will also be reset. - - @return A `std::optional` set of peers which do not need to be - relayed to. If the result is unseated, the item should - _not_ be relayed. - */ + /** + * Determines whether the hashed item should be relayed. + * + * Effects: + * + * If the item should be relayed, this function will not + * return a seated optional again until the relay time has expired. + * The internal set of peers will also be reset. + * + * @return A `std::optional` set of peers which do not need to be + * relayed to. If the result is unseated, the item should + * _not_ be relayed. + */ std::optional> shouldRelay(uint256 const& key); diff --git a/include/xrpl/core/Job.h b/include/xrpl/core/Job.h index e16d7412bf..93b39701be 100644 --- a/include/xrpl/core/Job.h +++ b/include/xrpl/core/Job.h @@ -83,12 +83,13 @@ class Job : public CountedObject public: using clock_type = std::chrono::steady_clock; - /** Default constructor. - - Allows Job to be used as a container type. - - This is used to allow things like jobMap [key] = value. - */ + /** + * Default constructor. + * + * Allows Job to be used as a container type. + * + * This is used to allow things like jobMap [key] = value. + */ // VFALCO NOTE I'd prefer not to have a default constructed object. // What is the semantic meaning of a Job with no associated // function? Having the invariant "all Job objects refer to @@ -108,7 +109,9 @@ public: [[nodiscard]] JobType getType() const; - /** Returns the time when the job was queued. */ + /** + * Returns the time when the job was queued. + */ [[nodiscard]] clock_type::time_point const& queueTime() const; diff --git a/include/xrpl/core/JobQueue.h b/include/xrpl/core/JobQueue.h index e4b64546f3..0c9fc76357 100644 --- a/include/xrpl/core/JobQueue.h +++ b/include/xrpl/core/JobQueue.h @@ -45,20 +45,23 @@ struct CoroCreateT explicit CoroCreateT() = default; }; -/** A pool of threads to perform work. - - A job posted will always run to completion. - - Coroutines that are suspended must be resumed, - and run to completion. - - When the JobQueue stops, it waits for all jobs - and coroutines to finish. -*/ +/** + * A pool of threads to perform work. + * + * A job posted will always run to completion. + * + * Coroutines that are suspended must be resumed, + * and run to completion. + * + * When the JobQueue stops, it waits for all jobs + * and coroutines to finish. + */ class JobQueue : private Workers::Callback { public: - /** Coroutines must run to completion. */ + /** + * Coroutines must run to completion. + */ class Coro : public std::enable_shared_from_this { private: @@ -87,55 +90,64 @@ public: ~Coro(); - /** Suspend coroutine execution. - Effects: - The coroutine's stack is saved. - The associated Job thread is released. - Note: - The associated Job function returns. - Undefined behavior if called consecutively without a corresponding - post. - */ + /** + * Suspend coroutine execution. + * Effects: + * The coroutine's stack is saved. + * The associated Job thread is released. + * Note: + * The associated Job function returns. + * Undefined behavior if called consecutively without a corresponding + * post. + */ void yield() const; - /** Schedule coroutine execution. - Effects: - Returns immediately. - A new job is scheduled to resume the execution of the coroutine. - When the job runs, the coroutine's stack is restored and execution - continues at the beginning of coroutine function or the - statement after the previous call to yield. Undefined behavior if - called after the coroutine has completed with a return (as opposed to - a yield()). Undefined behavior if post() or resume() called - consecutively without a corresponding yield. - - @return true if the Coro's job is added to the JobQueue. - */ + /** + * Schedule coroutine execution. + * Effects: + * Returns immediately. + * A new job is scheduled to resume the execution of the coroutine. + * When the job runs, the coroutine's stack is restored and execution + * continues at the beginning of coroutine function or the + * statement after the previous call to yield. Undefined behavior if + * called after the coroutine has completed with a return (as opposed to + * a yield()). Undefined behavior if post() or resume() called + * consecutively without a corresponding yield. + * + * @return true if the Coro's job is added to the JobQueue. + */ bool post(); - /** Resume coroutine execution. - Effects: - The coroutine continues execution from where it last left off - using this same thread. - If the coroutine has already completed, returns immediately - (handles the documented post-before-yield race condition). - Undefined behavior if resume() or post() called consecutively - without a corresponding yield. - */ + /** + * Resume coroutine execution. + * Effects: + * The coroutine continues execution from where it last left off + * using this same thread. + * If the coroutine has already completed, returns immediately + * (handles the documented post-before-yield race condition). + * Undefined behavior if resume() or post() called consecutively + * without a corresponding yield. + */ void resume(); - /** Returns true if the Coro is still runnable (has not returned). */ + /** + * Returns true if the Coro is still runnable (has not returned). + */ [[nodiscard]] bool runnable() const; - /** Once called, the Coro allows early exit without an assert. */ + /** + * Once called, the Coro allows early exit without an assert. + */ void expectEarlyExit(); - /** Waits until coroutine returns from the user function. */ + /** + * Waits until coroutine returns from the user function. + */ void join(); }; @@ -150,14 +162,15 @@ public: perf::PerfLog& perfLog); ~JobQueue() override; - /** Adds a job to the JobQueue. - - @param type The type of job. - @param name Name of the job. - @param jobHandler Callable with signature void(). Called when the job is executed. - - @return true if jobHandler added to queue. - */ + /** + * Adds a job to the JobQueue. + * + * @param type The type of job. + * @param name Name of the job. + * @param jobHandler Callable with signature void(). Called when the job is executed. + * + * @return true if jobHandler added to queue. + */ template bool addJob(JobType type, std::string const& name, JobHandler&& jobHandler) @@ -170,40 +183,46 @@ public: return false; } - /** Creates a coroutine and adds a job to the queue which will run it. - - @param t The type of job. - @param name Name of the job. - @param f Has a signature of void(std::shared_ptr). Called when the - job executes. - - @return shared_ptr to posted Coro. nullptr if post was not successful. - */ + /** + * Creates a coroutine and adds a job to the queue which will run it. + * + * @param t The type of job. + * @param name Name of the job. + * @param f Has a signature of void(std::shared_ptr). Called when the + * job executes. + * + * @return shared_ptr to posted Coro. nullptr if post was not successful. + */ template std::shared_ptr postCoro(JobType t, std::string const& name, F&& f); - /** Jobs waiting at this priority. + /** + * Jobs waiting at this priority. */ int getJobCount(JobType t) const; - /** Jobs waiting plus running at this priority. + /** + * Jobs waiting plus running at this priority. */ int getJobCountTotal(JobType t) const; - /** All waiting jobs at or greater than this priority. + /** + * All waiting jobs at or greater than this priority. */ int getJobCountGE(JobType t) const; - /** Return a scoped LoadEvent. + /** + * Return a scoped LoadEvent. */ std::unique_ptr makeLoadEvent(JobType t, std::string const& name); - /** Add multiple load events. + /** + * Add multiple load events. */ void addLoadEvents(JobType t, int count, std::chrono::milliseconds elapsed); @@ -216,7 +235,9 @@ public: json::Value getJson(int c = 0); - /** Block until no jobs running. */ + /** + * Block until no jobs running. + */ void rendezvous(); diff --git a/include/xrpl/core/JobTypeInfo.h b/include/xrpl/core/JobTypeInfo.h index b5db0dbaab..302a462ac6 100644 --- a/include/xrpl/core/JobTypeInfo.h +++ b/include/xrpl/core/JobTypeInfo.h @@ -8,21 +8,26 @@ namespace xrpl { -/** Holds all the 'static' information about a job, which does not change */ +/** + * Holds all the 'static' information about a job, which does not change + */ class JobTypeInfo { private: JobType const type_; std::string const name_; - /** The limit on the number of running jobs for this job type. - - A limit of 0 marks this as a "special job" which is not - dispatched via the job queue. + /** + * The limit on the number of running jobs for this job type. + * + * A limit of 0 marks this as a "special job" which is not + * dispatched via the job queue. */ int const limit_; - /** Average and peak latencies for this job type. 0 is none specified */ + /** + * Average and peak latencies for this job type. 0 is none specified + */ std::chrono::milliseconds const avgLatency_; std::chrono::milliseconds const peakLatency_; diff --git a/include/xrpl/core/NetworkIDService.h b/include/xrpl/core/NetworkIDService.h index 009f9ba6f8..8e2b3fcfe2 100644 --- a/include/xrpl/core/NetworkIDService.h +++ b/include/xrpl/core/NetworkIDService.h @@ -4,25 +4,27 @@ namespace xrpl { -/** Service that provides access to the network ID. - - This service provides read-only access to the network ID configured - for this server. The network ID identifies which network (mainnet, - testnet, devnet, or custom network) this server is configured to - connect to. - - Well-known network IDs: - - 0: Mainnet - - 1: Testnet - - 2: Devnet - - 1025+: Custom networks (require NetworkID field in transactions) -*/ +/** + * Service that provides access to the network ID. + * + * This service provides read-only access to the network ID configured + * for this server. The network ID identifies which network (mainnet, + * testnet, devnet, or custom network) this server is configured to + * connect to. + * + * Well-known network IDs: + * - 0: Mainnet + * - 1: Testnet + * - 2: Devnet + * - 1025+: Custom networks (require NetworkID field in transactions) + */ class NetworkIDService { public: virtual ~NetworkIDService() = default; - /** Get the configured network ID + /** + * Get the configured network ID * * @return The network ID this server is configured for */ diff --git a/include/xrpl/core/PeerReservationTable.h b/include/xrpl/core/PeerReservationTable.h index e6f6dd622e..c95c88b967 100644 --- a/include/xrpl/core/PeerReservationTable.h +++ b/include/xrpl/core/PeerReservationTable.h @@ -81,7 +81,7 @@ public: /** * @return the replaced reservation if it existed - * @throw soci::soci_error + * @throws soci::soci_error */ std::optional insertOrAssign(PeerReservation const& reservation); diff --git a/include/xrpl/core/ServiceRegistry.h b/include/xrpl/core/ServiceRegistry.h index 50bf2d7c10..592964134b 100644 --- a/include/xrpl/core/ServiceRegistry.h +++ b/include/xrpl/core/ServiceRegistry.h @@ -83,17 +83,17 @@ using RCLValidations = Validations; using NodeCache = TaggedCache; -/** Service registry for dependency injection. - - This abstract interface provides access to various services and components - used throughout the application. It separates the service locator pattern - from the Application lifecycle management. - - Components that need access to services can hold a reference to - ServiceRegistry rather than Application when they only need service - access and not lifecycle management. - -*/ +/** + * Service registry for dependency injection. + * + * This abstract interface provides access to various services and components + * used throughout the application. It separates the service locator pattern + * from the Application lifecycle management. + * + * Components that need access to services can hold a reference to + * ServiceRegistry rather than Application when they only need service + * access and not lifecycle management. + */ class ServiceRegistry { public: @@ -240,7 +240,9 @@ public: [[nodiscard]] virtual std::optional const& getTrapTxID() const = 0; - /** Retrieve the "wallet database" */ + /** + * Retrieve the "wallet database" + */ virtual DatabaseCon& getWalletDB() = 0; diff --git a/include/xrpl/core/detail/Workers.h b/include/xrpl/core/detail/Workers.h index d20ebf7a64..6829d5a14b 100644 --- a/include/xrpl/core/detail/Workers.h +++ b/include/xrpl/core/detail/Workers.h @@ -60,7 +60,9 @@ class PerfLog; class Workers { public: - /** Called to perform tasks as needed. */ + /** + * Called to perform tasks as needed. + */ struct Callback { virtual ~Callback() = default; @@ -69,27 +71,29 @@ public: Callback& operator=(Callback const&) = delete; - /** Perform a task. - - The call is made on a thread owned by Workers. It is important - that you only process one task from inside your callback. Each - call to addTask will result in exactly one call to processTask. - - @param instance The worker thread instance. - - @see Workers::addTask - */ + /** + * Perform a task. + * + * The call is made on a thread owned by Workers. It is important + * that you only process one task from inside your callback. Each + * call to addTask will result in exactly one call to processTask. + * + * @param instance The worker thread instance. + * + * @see Workers::addTask + */ virtual void processTask(int instance) = 0; }; - /** Create the object. - - A number of initial threads may be optionally specified. The - default is to create one thread per CPU. - - @param threadNames The name given to each created worker thread. - */ + /** + * Create the object. + * + * A number of initial threads may be optionally specified. The + * default is to create one thread per CPU. + * + * @param threadNames The name given to each created worker thread. + */ explicit Workers( Callback& callback, perf::PerfLog* perfLog, @@ -98,49 +102,54 @@ public: ~Workers(); - /** Retrieve the desired number of threads. - - This just returns the number of active threads that were requested. If - there was a recent call to setNumberOfThreads, the actual number of - active threads may be temporarily different from what was last requested. - - @note This function is not thread-safe. - */ + /** + * Retrieve the desired number of threads. + * + * This just returns the number of active threads that were requested. If + * there was a recent call to setNumberOfThreads, the actual number of + * active threads may be temporarily different from what was last requested. + * + * @note This function is not thread-safe. + */ [[nodiscard]] int getNumberOfThreads() const noexcept; - /** Set the desired number of threads. - @note This function is not thread-safe. - */ + /** + * Set the desired number of threads. + * @note This function is not thread-safe. + */ void setNumberOfThreads(int numberOfThreads); - /** Pause all threads and wait until they are paused. - - If a thread is processing a task it will pause as soon as the task - completes. There may still be tasks signaled even after all threads - have paused. - - @note This function is not thread-safe. - */ + /** + * Pause all threads and wait until they are paused. + * + * If a thread is processing a task it will pause as soon as the task + * completes. There may still be tasks signaled even after all threads + * have paused. + * + * @note This function is not thread-safe. + */ void stop(); - /** Add a task to be performed. - - Every call to addTask will eventually result in a call to - Callback::processTask unless the Workers object is destroyed or - the number of threads is never set above zero. - - @note This function is thread-safe. - */ + /** + * Add a task to be performed. + * + * Every call to addTask will eventually result in a call to + * Callback::processTask unless the Workers object is destroyed or + * the number of threads is never set above zero. + * + * @note This function is thread-safe. + */ void addTask(); - /** Get the number of currently executing calls of Callback::processTask. - While this function is thread-safe, the value may not stay - accurate for very long. It's mainly for diagnostic purposes. - */ + /** + * Get the number of currently executing calls of Callback::processTask. + * While this function is thread-safe, the value may not stay + * accurate for very long. It's mainly for diagnostic purposes. + */ [[nodiscard]] int numberOfCurrentlyRunningTasks() const noexcept; diff --git a/include/xrpl/core/detail/semaphore.h b/include/xrpl/core/detail/semaphore.h index e40463e322..abf6705097 100644 --- a/include/xrpl/core/detail/semaphore.h +++ b/include/xrpl/core/detail/semaphore.h @@ -45,14 +45,17 @@ private: public: using size_type = std::size_t; - /** Create the semaphore, with an optional initial count. - If unspecified, the initial count is zero. - */ + /** + * Create the semaphore, with an optional initial count. + * If unspecified, the initial count is zero. + */ explicit BasicSemaphore(size_type count = 0) : count_(count) { } - /** Increment the count and unblock one waiting thread. */ + /** + * Increment the count and unblock one waiting thread. + */ void notify() { @@ -61,7 +64,9 @@ public: cond_.notify_one(); } - /** Block until notify is called. */ + /** + * Block until notify is called. + */ void wait() { @@ -71,9 +76,10 @@ public: --count_; } - /** Perform a non-blocking wait. - @return `true` If the wait would be satisfied. - */ + /** + * Perform a non-blocking wait. + * @return `true` If the wait would be satisfied. + */ bool tryWait() { diff --git a/include/xrpl/crypto/RFC1751.h b/include/xrpl/crypto/RFC1751.h index 278f3c207b..3de65c3028 100644 --- a/include/xrpl/crypto/RFC1751.h +++ b/include/xrpl/crypto/RFC1751.h @@ -16,13 +16,14 @@ public: static void getEnglishFromKey(std::string& strHuman, std::string const& strKey); - /** Chooses a single dictionary word from the data. - - This is not particularly secure but it can be useful to provide - a unique name for something given a GUID or fixed data. We use - it to turn the pubkey_node into an easily remembered and identified - 4 character string. - */ + /** + * Chooses a single dictionary word from the data. + * + * This is not particularly secure but it can be useful to provide + * a unique name for something given a GUID or fixed data. We use + * it to turn the pubkey_node into an easily remembered and identified + * 4 character string. + */ static std::string getWordFromBlob(void const* blob, size_t bytes); diff --git a/include/xrpl/crypto/csprng.h b/include/xrpl/crypto/csprng.h index cdc6a723c8..e19d33a464 100644 --- a/include/xrpl/crypto/csprng.h +++ b/include/xrpl/crypto/csprng.h @@ -7,14 +7,15 @@ namespace xrpl { -/** A cryptographically secure random number engine - - The engine is thread-safe (it uses a lock to serialize - access) and will, automatically, mix in some randomness - from std::random_device. - - Meets the requirements of UniformRandomNumberEngine -*/ +/** + * A cryptographically secure random number engine + * + * The engine is thread-safe (it uses a lock to serialize + * access) and will, automatically, mix in some randomness + * from std::random_device. + * + * Meets the requirements of UniformRandomNumberEngine + */ class CsprngEngine { private: @@ -34,15 +35,21 @@ public: CsprngEngine(); ~CsprngEngine(); - /** Mix entropy into the pool */ + /** + * Mix entropy into the pool + */ void mixEntropy(void* buffer = nullptr, std::size_t count = 0); - /** Generate a random integer */ + /** + * Generate a random integer + */ result_type operator()(); - /** Fill a buffer with the requested amount of random data */ + /** + * Fill a buffer with the requested amount of random data + */ void operator()(void* ptr, std::size_t count); @@ -61,14 +68,15 @@ public: } }; -/** The default cryptographically secure PRNG - - Use this when you need to generate random numbers or - data that will be used for encryption or passed into - cryptographic routines. - - This meets the requirements of UniformRandomNumberEngine -*/ +/** + * The default cryptographically secure PRNG + * + * Use this when you need to generate random numbers or + * data that will be used for encryption or passed into + * cryptographic routines. + * + * This meets the requirements of UniformRandomNumberEngine + */ CsprngEngine& cryptoPrng(); diff --git a/include/xrpl/crypto/secure_erase.h b/include/xrpl/crypto/secure_erase.h index 74284b03f7..38531afc1d 100644 --- a/include/xrpl/crypto/secure_erase.h +++ b/include/xrpl/crypto/secure_erase.h @@ -4,20 +4,21 @@ namespace xrpl { -/** Attempts to clear the given blob of memory. - - The underlying implementation of this function takes pains to - attempt to outsmart the compiler from optimizing the clearing - away. Please note that, despite that, remnants of content may - remain floating around in memory as well as registers, caches - and more. - - For a more in-depth discussion of the subject please see the - below posts by Colin Percival: - - http://www.daemonology.net/blog/2014-09-04-how-to-zero-a-buffer.html - http://www.daemonology.net/blog/2014-09-06-zeroing-buffers-is-insufficient.html -*/ +/** + * Attempts to clear the given blob of memory. + * + * The underlying implementation of this function takes pains to + * attempt to outsmart the compiler from optimizing the clearing + * away. Please note that, despite that, remnants of content may + * remain floating around in memory as well as registers, caches + * and more. + * + * For a more in-depth discussion of the subject please see the + * below posts by Colin Percival: + * + * http://www.daemonology.net/blog/2014-09-04-how-to-zero-a-buffer.html + * http://www.daemonology.net/blog/2014-09-06-zeroing-buffers-is-insufficient.html + */ void secureErase(void* dest, std::size_t bytes); diff --git a/include/xrpl/json/JsonPropertyStream.h b/include/xrpl/json/JsonPropertyStream.h index 405a61cd34..498283c16b 100644 --- a/include/xrpl/json/JsonPropertyStream.h +++ b/include/xrpl/json/JsonPropertyStream.h @@ -8,7 +8,9 @@ namespace xrpl { -/** A PropertyStream::Sink which produces a json::Value of type ValueType::Object. */ +/** + * A PropertyStream::Sink which produces a json::Value of type ValueType::Object. + */ class JsonPropertyStream : public beast::PropertyStream { public: diff --git a/include/xrpl/json/Output.h b/include/xrpl/json/Output.h index c01253f713..53d453c277 100644 --- a/include/xrpl/json/Output.h +++ b/include/xrpl/json/Output.h @@ -17,18 +17,20 @@ stringOutput(std::string& s) return [&](boost::beast::string_view const& b) { s.append(b.data(), b.size()); }; } -/** Writes a minimal representation of a Json value to an Output in O(n) time. - - Data is streamed right to the output, so only a marginal amount of memory is - used. This can be very important for a very large json::Value. +/** + * Writes a minimal representation of a Json value to an Output in O(n) time. + * + * Data is streamed right to the output, so only a marginal amount of memory is + * used. This can be very important for a very large json::Value. */ void outputJson(json::Value const&, Output const&); -/** Return the minimal string representation of a json::Value in O(n) time. - - This requires a memory allocation for the full size of the output. - If possible, use outputJson(). +/** + * Return the minimal string representation of a json::Value in O(n) time. + * + * This requires a memory allocation for the full size of the output. + * If possible, use outputJson(). */ std::string jsonAsString(json::Value const&); diff --git a/include/xrpl/json/Writer.h b/include/xrpl/json/Writer.h index 024876a43c..ec7fd6a0d2 100644 --- a/include/xrpl/json/Writer.h +++ b/include/xrpl/json/Writer.h @@ -12,98 +12,98 @@ namespace json { /** - * Writer implements an O(1)-space, O(1)-granular output JSON writer. + * Writer implements an O(1)-space, O(1)-granular output JSON writer. * - * O(1)-space means that it uses a fixed amount of memory, and that there are - * no heap allocations at each step of the way. + * O(1)-space means that it uses a fixed amount of memory, and that there are + * no heap allocations at each step of the way. * - * O(1)-granular output means the writer only outputs in small segments of a - * bounded size, using a bounded number of CPU cycles in doing so. This is - * very helpful in scheduling long jobs. + * O(1)-granular output means the writer only outputs in small segments of a + * bounded size, using a bounded number of CPU cycles in doing so. This is + * very helpful in scheduling long jobs. * - * The tradeoff is that you have to fill items in the JSON tree as you go, - * and you can never go backward. + * The tradeoff is that you have to fill items in the JSON tree as you go, + * and you can never go backward. * - * Writer can write single JSON tokens, but the typical use is to write out an - * entire JSON object. For example: + * Writer can write single JSON tokens, but the typical use is to write out an + * entire JSON object. For example: * - * { - * Writer w (out); + * { + * Writer w (out); * - * w.startObject (); // Start the root object. - * w.set ("hello", "world"); - * w.set ("goodbye", 23); - * w.finishObject (); // Finish the root object. - * } + * w.startObject (); // Start the root object. + * w.set ("hello", "world"); + * w.set ("goodbye", 23); + * w.finishObject (); // Finish the root object. + * } * - * which outputs the string + * which outputs the string * - * {"hello":"world","goodbye":23} + * {"hello":"world","goodbye":23} * - * There can be an object inside an object: + * There can be an object inside an object: * - * { - * Writer w (out); + * { + * Writer w (out); * - * w.startObject (); // Start the root object. - * w.set ("hello", "world"); + * w.startObject (); // Start the root object. + * w.set ("hello", "world"); * - * w.startObjectSet ("subobject"); // Start a sub-object. - * w.set ("goodbye", 23); // Add a key, value assignment. - * w.finishObject (); // Finish the sub-object. + * w.startObjectSet ("subobject"); // Start a sub-object. + * w.set ("goodbye", 23); // Add a key, value assignment. + * w.finishObject (); // Finish the sub-object. * - * w.finishObject (); // Finish the root-object. - * } + * w.finishObject (); // Finish the root-object. + * } * - * which outputs the string + * which outputs the string * - * {"hello":"world","subobject":{"goodbye":23}}. + * {"hello":"world","subobject":{"goodbye":23}}. * - * Arrays work similarly + * Arrays work similarly * - * { - * Writer w (out); - * w.startObject (); // Start the root object. + * { + * Writer w (out); + * w.startObject (); // Start the root object. * - * w.startArraySet ("hello"); // Start an array. - * w.append (23) // Append some items. - * w.append ("skidoo") - * w.finishArray (); // Finish the array. + * w.startArraySet ("hello"); // Start an array. + * w.append (23) // Append some items. + * w.append ("skidoo") + * w.finishArray (); // Finish the array. * - * w.finishObject (); // Finish the root object. - * } + * w.finishObject (); // Finish the root object. + * } * - * which outputs the string + * which outputs the string * - * {"hello":[23,"skidoo"]}. + * {"hello":[23,"skidoo"]}. * * - * If you've reached the end of a long object, you can just use finishAll() - * which finishes all arrays and objects that you have started. + * If you've reached the end of a long object, you can just use finishAll() + * which finishes all arrays and objects that you have started. * - * { - * Writer w (out); - * w.startObject (); // Start the root object. + * { + * Writer w (out); + * w.startObject (); // Start the root object. * - * w.startArraySet ("hello"); // Start an array. - * w.append (23) // Append an item. + * w.startArraySet ("hello"); // Start an array. + * w.append (23) // Append an item. * - * w.startArrayAppend () // Start a sub-array. - * w.append ("one"); - * w.append ("two"); + * w.startArrayAppend () // Start a sub-array. + * w.append ("one"); + * w.append ("two"); * - * w.startObjectAppend (); // Append a sub-object. - * w.finishAll (); // Finish everything. - * } + * w.startObjectAppend (); // Append a sub-object. + * w.finishAll (); // Finish everything. + * } * - * which outputs the string + * which outputs the string * - * {"hello":[23,["one","two",{}]]}. + * {"hello":[23,["one","two",{}]]}. * - * For convenience, the destructor of Writer calls w.finishAll() which makes - * sure that all arrays and objects are closed. This means that you can throw - * an exception, or have a coroutine simply clean up the stack, and be sure - * that you do in fact generate a complete JSON object. + * For convenience, the destructor of Writer calls w.finishAll() which makes + * sure that all arrays and objects are closed. This means that you can throw + * an exception, or have a coroutine simply clean up the stack, and be sure + * that you do in fact generate a complete JSON object. */ class Writer @@ -118,26 +118,37 @@ public: ~Writer(); - /** Start a new collection at the root level. */ + /** + * Start a new collection at the root level. + */ void startRoot(CollectionType); - /** Start a new collection inside an array. */ + /** + * Start a new collection inside an array. + */ void startAppend(CollectionType); - /** Start a new collection inside an object. */ + /** + * Start a new collection inside an object. + */ void startSet(CollectionType, std::string const& key); - /** Finish the collection most recently started. */ + /** + * Finish the collection most recently started. + */ void finish(); - /** Finish all objects and arrays. After finishArray() has been called, no - * more operations can be performed. */ + /** + * Finish all objects and arrays. After finishArray() has been called, no + * more operations can be performed. + */ void finishAll(); - /** Append a value to an array. + /** + * Append a value to an array. * * Scalar must be a scalar - that is, a number, boolean, string, string * literal, nullptr or json::Value @@ -150,12 +161,15 @@ public: output(t); } - /** Add a comma before this next item if not the first item in an array. - Useful if you are writing the actual array yourself. */ + /** + * Add a comma before this next item if not the first item in an array. + * Useful if you are writing the actual array yourself. + */ void rawAppend(); - /** Add a key, value assignment to an object. + /** + * Add a key, value assignment to an object. * * Scalar must be a scalar - that is, a number, boolean, string, string * literal, or nullptr. @@ -174,8 +188,10 @@ public: output(t); } - /** Emit just "tag": as part of an object. Useful if you are writing the - actual value data yourself. */ + /** + * Emit just "tag": as part of an object. Useful if you are writing the + * actual value data yourself. + */ void rawSet(std::string const& key); @@ -194,22 +210,32 @@ public: void output(json::Value const&); - /** Output a null. */ + /** + * Output a null. + */ void output(std::nullptr_t); - /** Output a float. */ + /** + * Output a float. + */ void output(float); - /** Output a double. */ + /** + * Output a double. + */ void output(double); - /** Output a bool. */ + /** + * Output a bool. + */ void output(bool); - /** Output numbers or booleans. */ + /** + * Output numbers or booleans. + */ template void output(Type t) diff --git a/include/xrpl/json/json_reader.h b/include/xrpl/json/json_reader.h index d1e4ada579..ed60f49ce4 100644 --- a/include/xrpl/json/json_reader.h +++ b/include/xrpl/json/json_reader.h @@ -12,9 +12,9 @@ namespace json { -/** \brief Unserialize a JSON document into a +/** + * @brief Unserialize a JSON document into a * Value. - * */ class Reader { @@ -22,48 +22,55 @@ public: using Char = char; using Location = Char const*; - /** \brief Constructs a Reader allowing all features + /** + * @brief Constructs a Reader allowing all features * for parsing. */ Reader() = default; - /** \brief Read a Value from a JSON - * document. \param document UTF-8 encoded string containing the document to - * read. \param root [out] Contains the root value of the document if it was + /** + * @brief Read a Value from a JSON + * document. @param document UTF-8 encoded string containing the document to + * read. @param root [out] Contains the root value of the document if it was * successfully parsed. - * \return \c true if the document was successfully parsed, \c false if an + * @return @c true if the document was successfully parsed, @c false if an * error occurred. */ bool parse(std::string const& document, Value& root); - /** \brief Read a Value from a JSON - * document. \param document UTF-8 encoded string containing the document to - * read. \param root [out] Contains the root value of the document if it was + /** + * @brief Read a Value from a JSON + * document. @param document UTF-8 encoded string containing the document to + * read. @param root [out] Contains the root value of the document if it was * successfully parsed. - * \return \c true if the document was successfully parsed, \c false if an + * @return @c true if the document was successfully parsed, @c false if an * error occurred. */ bool parse(char const* beginDoc, char const* endDoc, Value& root); - /// \brief Parse from input stream. - /// \see json::operator>>(std::istream&, json::Value&). + /** + * @brief Parse from input stream. + * @see json::operator>>(std::istream&, json::Value&). + */ bool parse(std::istream& is, Value& root); - /** \brief Read a Value from a JSON buffer - * sequence. \param root [out] Contains the root value of the document if it - * was successfully parsed. \param UTF-8 encoded buffer sequence. \return \c - * true if the buffer was successfully parsed, \c false if an error + /** + * @brief Read a Value from a JSON buffer + * sequence. @param root [out] Contains the root value of the document if it + * was successfully parsed. @param UTF-8 encoded buffer sequence. @return @c + * true if the buffer was successfully parsed, @c false if an error * occurred. */ template bool parse(Value& root, BufferSequence const& bs); - /** \brief Returns a user friendly string that list errors in the parsed - * document. \return Formatted error message with the list of errors with + /** + * @brief Returns a user friendly string that list errors in the parsed + * document. @return Formatted error message with the list of errors with * their location in the parsed document. An empty string is returned if no * error occurred during parsing. */ @@ -195,30 +202,31 @@ Reader::parse(Value& root, BufferSequence const& bs) return parse(s, root); } -/** \brief Read from 'sin' into 'root'. - - Always keep comments from the input JSON. - - This can be used to read a file into a particular sub-object. - For example: - \code - json::Value root; - cin >> root["dir"]["file"]; - cout << root; - \endcode - Result: - \verbatim - { -"dir": { - "file": { - // The input stream JSON would be nested here. - } -} - } - \endverbatim - \throw std::exception on parse error. - \see json::operator<<() -*/ +/** + * @brief Read from 'sin' into 'root'. + * + * Always keep comments from the input JSON. + * + * This can be used to read a file into a particular sub-object. + * For example: + * @code + * json::Value root; + * cin >> root["dir"]["file"]; + * cout << root; + * @endcode + * Result: + * @verbatim + * { + * "dir": { + * "file": { + * // The input stream JSON would be nested here. + * } + * } + * } + * @endverbatim + * @throws std::exception on parse error. + * @see json::operator<<() + */ std::istream& operator>>(std::istream&, Value&); diff --git a/include/xrpl/json/json_value.h b/include/xrpl/json/json_value.h index f786c6a9dc..47ad3ac1e0 100644 --- a/include/xrpl/json/json_value.h +++ b/include/xrpl/json/json_value.h @@ -9,11 +9,13 @@ #include #include -/** \brief JSON (JavaScript Object Notation). +/** + * @brief JSON (JavaScript Object Notation). */ namespace json { -/** \brief Type of the value held by a Value object. +/** + * @brief Type of the value held by a Value object. */ enum class ValueType { Null = 0, ///< 'null' value @@ -26,19 +28,20 @@ enum class ValueType { Object ///< object value (collection of name/value pairs). }; -/** \brief Lightweight wrapper to tag static string. +/** + * @brief Lightweight wrapper to tag static string. * * Value constructor and ValueType::Object member assignment takes advantage of the * StaticString and avoid the cost of string duplication when storing the * string or the member name. * * Example of usage: - * \code + * @code * json::Value aValue( StaticString("some text") ); * json::Value object; * static const StaticString code("code"); * object[code] = 1234; - * \endcode + * @endcode */ class StaticString { @@ -99,7 +102,8 @@ operator!=(StaticString x, std::string const& y) return !(y == x); } -/** \brief Represents a JSON value. +/** + * @brief Represents a JSON value. * * This class is a discriminated union wrapper that can represent a: * - signed integer [range: Value::kMinInt - Value::kMaxInt] @@ -175,37 +179,39 @@ public: using ObjectValues = std::map; public: - /** \brief Create a default Value of the given type. - - This is a very useful constructor. - To create an empty array, pass ValueType::Array. - To create an empty object, pass ValueType::Object. - Another Value can then be set to this one by assignment. - This is useful since clear() and resize() will not alter types. - - Examples: - \code - json::Value null_value; // null - json::Value arr_value(json::ValueType::Array); // [] - json::Value obj_value(json::ValueType::Object); // {} - \endcode - */ + /** + * @brief Create a default Value of the given type. + * + * This is a very useful constructor. + * To create an empty array, pass ValueType::Array. + * To create an empty object, pass ValueType::Object. + * Another Value can then be set to this one by assignment. + * This is useful since clear() and resize() will not alter types. + * + * Examples: + * @code + * json::Value null_value; // null + * json::Value arr_value(json::ValueType::Array); // [] + * json::Value obj_value(json::ValueType::Object); // {} + * @endcode + */ Value(ValueType type = ValueType::Null); Value(Int value); Value(UInt value); Value(double value); Value(char const* value); Value(xrpl::Number const& value); - /** \brief Constructs a value from a static string. - + /** + * @brief Constructs a value from a static string. + * * Like other value string constructor but do not duplicate the string for * internal storage. The given string must remain alive after the call to - this + * this * constructor. * Example of usage: - * \code + * @code * json::Value aValue( StaticString("some text") ); - * \endcode + * @endcode */ Value(StaticString const& value); Value(std::string const& value); @@ -220,7 +226,9 @@ public: Value(Value&& other) noexcept; - /// Swap values. + /** + * Swap values. + */ void swap(Value& other) noexcept; @@ -229,7 +237,9 @@ public: [[nodiscard]] char const* asCString() const; - /** Returns the unquoted string value. */ + /** + * Returns the unquoted string value. + */ [[nodiscard]] std::string asString() const; [[nodiscard]] Int @@ -241,13 +251,17 @@ public: [[nodiscard]] bool asBool() const; - /** Correct absolute value from int or unsigned int */ + /** + * Correct absolute value from int or unsigned int + */ [[nodiscard]] UInt asAbsUInt() const; // TODO: What is the "empty()" method this docstring mentions? - /** isNull() tests to see if this field is null. Don't use this method to - test for emptiness: use empty(). */ + /** + * isNull() tests to see if this field is null. Don't use this method to + * test for emptiness: use empty(). + */ [[nodiscard]] bool isNull() const; [[nodiscard]] bool @@ -276,116 +290,157 @@ public: [[nodiscard]] bool isConvertibleTo(ValueType other) const; - /// Number of values in array or object + /** + * Number of values in array or object + */ [[nodiscard]] UInt size() const; - /** Returns false if this is an empty array, empty object, empty string, - or null. */ + /** + * Returns false if this is an empty array, empty object, empty string, + * or null. + */ explicit operator bool() const; - /// Remove all object members and array elements. - /// \pre type() is ValueType::Array, ValueType::Object, or ValueType::Null - /// \post type() is unchanged + /** + * Remove all object members and array elements. + * @pre type() is ValueType::Array, ValueType::Object, or ValueType::Null + * @post type() is unchanged + */ void clear(); - /// Access an array element (zero based index ). - /// If the array contains less than index element, then null value are - /// inserted in the array so that its size is index+1. (You may need to say - /// 'value[0u]' to get your compiler to distinguish - /// this from the operator[] which takes a string.) + /** + * Access an array element (zero based index ). + * If the array contains less than index element, then null value are + * inserted in the array so that its size is index+1. (You may need to say + * 'value[0u]' to get your compiler to distinguish + * this from the operator[] which takes a string.) + */ Value& operator[](UInt index); - /// Access an array element (zero based index ) - /// (You may need to say 'value[0u]' to get your compiler to distinguish - /// this from the operator[] which takes a string.) + /** + * Access an array element (zero based index ) + * (You may need to say 'value[0u]' to get your compiler to distinguish + * this from the operator[] which takes a string.) + */ Value const& operator[](UInt index) const; - /// If the array contains at least index+1 elements, returns the element - /// value, otherwise returns defaultValue. + /** + * If the array contains at least index+1 elements, returns the element + * value, otherwise returns defaultValue. + */ [[nodiscard]] Value get(UInt index, Value const& defaultValue) const; - /// Return true if index < size(). + /** + * Return true if index < size(). + */ [[nodiscard]] bool isValidIndex(UInt index) const; - /// \brief Append value to array at the end. - /// - /// Equivalent to jsonvalue[jsonvalue.size()] = value; + /** + * @brief Append value to array at the end. + * + * Equivalent to jsonvalue[jsonvalue.size()] = value; + */ Value& append(Value const& value); Value& append(Value&& value); - /// Access an object value by name, create a null member if it does not - /// exist. + /** + * Access an object value by name, create a null member if it does not + * exist. + */ Value& operator[](char const* key); - /// Access an object value by name, returns null if there is no member with - /// that name. + /** + * Access an object value by name, returns null if there is no member with + * that name. + */ Value const& operator[](char const* key) const; - /// Access an object value by name, create a null member if it does not - /// exist. + /** + * Access an object value by name, create a null member if it does not + * exist. + */ Value& operator[](std::string const& key); - /// Access an object value by name, returns null if there is no member with - /// that name. + /** + * Access an object value by name, returns null if there is no member with + * that name. + */ Value const& operator[](std::string const& key) const; - /** \brief Access an object value by name, create a null member if it does - not exist. - + /** + * @brief Access an object value by name, create a null member if it does + * not exist. + * * If the object as no entry for that name, then the member name used to - store + * store * the new entry is not duplicated. * Example of use: - * \code + * @code * json::Value object; * static const StaticString code("code"); * object[code] = 1234; - * \endcode + * @endcode */ Value& operator[](StaticString const& key); Value const& operator[](StaticString const& key) const; - /// Return the member named key if it exist, defaultValue otherwise. + /** + * Return the member named key if it exist, defaultValue otherwise. + */ Value get(char const* key, Value const& defaultValue) const; - /// Return the member named key if it exist, defaultValue otherwise. + /** + * Return the member named key if it exist, defaultValue otherwise. + */ [[nodiscard]] Value get(std::string const& key, Value const& defaultValue) const; - /// \brief Remove and return the named member. - /// - /// Do nothing if it did not exist. - /// \return the removed Value, or null. - /// \pre type() is ValueType::Object or ValueType::Null - /// \post type() is unchanged + /** + * @brief Remove and return the named member. + * + * Do nothing if it did not exist. + * @return the removed Value, or null. + * @pre type() is ValueType::Object or ValueType::Null + * @post type() is unchanged + */ Value removeMember(char const* key); - /// Same as removeMember(const char*) + /** + * Same as removeMember(const char*) + */ Value removeMember(std::string const& key); - /// Return true if the object has a member named key. + /** + * Return true if the object has a member named key. + */ bool isMember(char const* key) const; - /// Return true if the object has a member named key. + /** + * Return true if the object has a member named key. + */ [[nodiscard]] bool isMember(std::string const& key) const; - /// Return true if the object has a member named key. + /** + * Return true if the object has a member named key. + */ [[nodiscard]] bool isMember(StaticString const& key) const; - /// \brief Return a list of the member names. - /// - /// If null, return an empty list. - /// \pre type() is ValueType::Object or ValueType::Null - /// \post if type() was ValueType::Null, it remains ValueType::Null + /** + * @brief Return a list of the member names. + * + * If null, return an empty list. + * @pre type() is ValueType::Object or ValueType::Null + * @post if type() was ValueType::Null, it remains ValueType::Null + */ [[nodiscard]] Members getMemberNames() const; @@ -461,7 +516,8 @@ operator>=(Value const& x, Value const& y) return !(x < y); } -/** \brief Experimental do not use: Allocator to customize member name and +/** + * @brief Experimental do not use: Allocator to customize member name and * string value memory management done by Value. * * - makeMemberName() and releaseMemberName() are called to respectively @@ -486,8 +542,8 @@ public: releaseStringValue(char* value) = 0; }; -/** \brief base class for Value iterators. - * +/** + * @brief base class for Value iterators. */ class ValueIteratorBase { @@ -512,17 +568,23 @@ public: return !isEqual(other); } - /// Return either the index or the member name of the referenced value as a - /// Value. + /** + * Return either the index or the member name of the referenced value as a + * Value. + */ [[nodiscard]] Value key() const; - /// Return the index of the referenced Value. -1 if it is not an ValueType::Array. + /** + * Return the index of the referenced Value. -1 if it is not an ValueType::Array. + */ [[nodiscard]] UInt index() const; - /// Return the member name of the referenced Value. "" if it is not an - /// ValueType::Object. + /** + * Return the member name of the referenced Value. "" if it is not an + * ValueType::Object. + */ [[nodiscard]] char const* memberName() const; @@ -551,8 +613,8 @@ private: bool isNull_; }; -/** \brief const iterator for object and array value. - * +/** + * @brief const iterator for object and array value. */ class ValueConstIterator : public ValueIteratorBase { @@ -569,7 +631,8 @@ public: ValueConstIterator(ValueConstIterator const& other) = default; private: - /*! \internal Use by Value to create an iterator. + /** + * @internal Use by Value to create an iterator. */ explicit ValueConstIterator(Value::ObjectValues::iterator const& current); @@ -614,7 +677,8 @@ public: } }; -/** \brief Iterator for object and array value. +/** + * @brief Iterator for object and array value. */ class ValueIterator : public ValueIteratorBase { @@ -632,7 +696,8 @@ public: ValueIterator(ValueIterator const& other); private: - /*! \internal Use by Value to create an iterator. + /** + * @internal Use by Value to create an iterator. */ explicit ValueIterator(Value::ObjectValues::iterator const& current); diff --git a/include/xrpl/json/json_writer.h b/include/xrpl/json/json_writer.h index 4bc15b71da..65c8b20931 100644 --- a/include/xrpl/json/json_writer.h +++ b/include/xrpl/json/json_writer.h @@ -13,7 +13,8 @@ namespace json { class Value; -/** \brief Abstract class for writers. +/** + * @brief Abstract class for writers. */ class WriterBase { @@ -23,12 +24,13 @@ public: write(Value const& root) = 0; }; -/** \brief Outputs a Value in JSON format +/** + * @brief Outputs a Value in JSON format * without formatting (not human friendly). * * The JSON document is written in a single line. It is not intended for 'human' * consumption, but may be useful to support feature such as RPC where bandwidth - * is limited. \sa Reader, Value + * is limited. @see Reader, Value */ class FastWriter : public WriterBase @@ -48,7 +50,8 @@ private: std::string document_; }; -/** \brief Writes a Value in JSON format in a +/** + * @brief Writes a Value in JSON format in a * human friendly way. * * The rules for line break and indent are as follow: @@ -64,7 +67,7 @@ private: * - otherwise, it the values do not fit on one line, or the array contains * object or non empty array, then print one value per line. * - * \sa Reader, Value + * @see Reader, Value */ class StyledWriter : public WriterBase { @@ -73,8 +76,9 @@ public: ~StyledWriter() override = default; public: // overridden from Writer - /** \brief Serialize a Value in JSON - * format. \param root Value to serialize. \return String containing the + /** + * @brief Serialize a Value in JSON + * format. @param root Value to serialize. @return String containing the * JSON document that represents the root value. */ std::string @@ -108,26 +112,27 @@ private: bool addChildValues_{}; }; -/** \brief Writes a Value in JSON format in a - human friendly way, to a stream rather than to a string. +/** + * @brief Writes a Value in JSON format in a + * human friendly way, to a stream rather than to a string. * * The rules for line break and indent are as follow: * - Object value: * - if empty then print {} without indent and line break * - if not empty the print '{', line break & indent, print one value per - line + * line * and then unindent and line break and print '}'. * - Array value: * - if empty then print [] without indent and line break * - if the array contains no object value, empty array or some other value - types, + * types, * and all the values fit on one lines, then print the array on a single - line. + * line. * - otherwise, it the values do not fit on one line, or the array contains * object or non empty array, then print one value per line. * - * \param indentation Each level will be indented by this amount extra. - * \sa Reader, Value + * @param indentation Each level will be indented by this amount extra. + * @see Reader, Value */ class StyledStreamWriter { @@ -136,10 +141,11 @@ public: ~StyledStreamWriter() = default; public: - /** \brief Serialize a Value in JSON - * format. \param out Stream to write to. (Can be ostringstream, e.g.) - * \param root Value to serialize. - * \note There is no point in deriving from Writer, since write() should not + /** + * @brief Serialize a Value in JSON + * format. @param out Stream to write to. (Can be ostringstream, e.g.) + * @param root Value to serialize. + * @note There is no point in deriving from Writer, since write() should not * return a value. */ void @@ -184,8 +190,10 @@ valueToString(bool value); std::string valueToQuotedString(char const* value); -/// \brief Output using the StyledStreamWriter. -/// \see json::operator>>() +/** + * @brief Output using the StyledStreamWriter. + * @see json::operator>>() + */ std::ostream& operator<<(std::ostream&, Value const& root); @@ -265,12 +273,13 @@ writeValue(Write const& write, Value const& value) } // namespace detail -/** Stream compact JSON to the specified function. - - @param jv The json::Value to write - @param write Invocable with signature void(void const*, std::size_t) that - is called when output should be written to the stream. -*/ +/** + * Stream compact JSON to the specified function. + * + * @param jv The json::Value to write + * @param write Invocable with signature void(void const*, std::size_t) that + * is called when output should be written to the stream. + */ template void stream(json::Value const& jv, Write const& write) @@ -279,29 +288,31 @@ stream(json::Value const& jv, Write const& write) write("\n", 1); } -/** Decorator for streaming out compact json - - Use - - json::Value jv; - out << json::Compact{jv} - - to write a single-line, compact version of `jv` to the stream, rather - than the styled format that comes from undecorated streaming. -*/ +/** + * Decorator for streaming out compact json + * + * Use + * + * json::Value jv; + * out << json::Compact{jv} + * + * to write a single-line, compact version of `jv` to the stream, rather + * than the styled format that comes from undecorated streaming. + */ class Compact { json::Value jv_; public: - /** Wrap a json::Value for compact streaming - - @param jv The json::Value to stream - - @note For now, we do not support wrapping lvalues to avoid - potentially costly copies. If we find a need, we can consider - adding support for compact lvalue streaming in the future. - */ + /** + * Wrap a json::Value for compact streaming + * + * @param jv The json::Value to stream + * + * @note For now, we do not support wrapping lvalues to avoid + * potentially costly copies. If we find a need, we can consider + * adding support for compact lvalue streaming in the future. + */ Compact(json::Value&& jv) : jv_{std::move(jv)} { } diff --git a/include/xrpl/json/to_string.h b/include/xrpl/json/to_string.h index 1d7b4c785a..bdd7a51e6a 100644 --- a/include/xrpl/json/to_string.h +++ b/include/xrpl/json/to_string.h @@ -6,11 +6,15 @@ namespace json { -/** Writes a json::Value to an std::string. */ +/** + * Writes a json::Value to an std::string. + */ std::string to_string(Value const&); -/** Writes a json::Value to an std::string. */ +/** + * Writes a json::Value to an std::string. + */ std::string pretty(Value const&); diff --git a/include/xrpl/ledger/AcceptedLedgerTx.h b/include/xrpl/ledger/AcceptedLedgerTx.h index f59b8a074d..283dcf6e24 100644 --- a/include/xrpl/ledger/AcceptedLedgerTx.h +++ b/include/xrpl/ledger/AcceptedLedgerTx.h @@ -21,17 +21,17 @@ namespace xrpl { /** - A transaction that is in a closed ledger. - - Description - - An accepted ledger transaction contains additional information that the - server needs to tell clients about the transaction. For example, - - The transaction in JSON form - - Which accounts are affected - * This is used by InfoSub to report to clients - - Cached stuff -*/ + * A transaction that is in a closed ledger. + * + * Description + * + * An accepted ledger transaction contains additional information that the + * server needs to tell clients about the transaction. For example, + * - The transaction in JSON form + * - Which accounts are affected + * * This is used by InfoSub to report to clients + * - Cached stuff + */ class AcceptedLedgerTx : public CountedObject { public: diff --git a/include/xrpl/ledger/AmendmentTable.h b/include/xrpl/ledger/AmendmentTable.h index 6598be5a5c..c3ef779eb1 100644 --- a/include/xrpl/ledger/AmendmentTable.h +++ b/include/xrpl/ledger/AmendmentTable.h @@ -37,10 +37,11 @@ namespace xrpl { class ServiceRegistry; -/** The amendment table stores the list of enabled and potential amendments. - Individuals amendments are voted on by validators during the consensus - process. -*/ +/** + * The amendment table stores the list of enabled and potential amendments. + * Individuals amendments are voted on by validators during the consensus + * process. + */ class AmendmentTable { public: @@ -90,11 +91,15 @@ public: [[nodiscard]] virtual json::Value getJson(bool isAdmin) const = 0; - /** Returns a json::ValueType::Object. */ + /** + * Returns a json::ValueType::Object. + */ [[nodiscard]] virtual json::Value getJson(uint256 const& amendment, bool isAdmin) const = 0; - /** Called when a new fully-validated ledger is accepted. */ + /** + * Called when a new fully-validated ledger is accepted. + */ void doValidatedLedger(std::shared_ptr const& lastValidatedLedger) { @@ -107,9 +112,10 @@ public: } } - /** Called to determine whether the amendment logic needs to process - a new validated ledger. (If it could have changed things.) - */ + /** + * Called to determine whether the amendment logic needs to process + * a new validated ledger. (If it could have changed things.) + */ [[nodiscard]] virtual bool needValidatedLedger(LedgerIndex seq) const = 0; diff --git a/include/xrpl/ledger/ApplyView.h b/include/xrpl/ledger/ApplyView.h index e519013d9a..724d89b7c6 100644 --- a/include/xrpl/ledger/ApplyView.h +++ b/include/xrpl/ledger/ApplyView.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include // IWYU pragma: keep @@ -11,6 +12,7 @@ #include #include #include +#include #include #include @@ -90,47 +92,50 @@ operator&=(ApplyFlags& lhs, ApplyFlags const& rhs) //------------------------------------------------------------------------------ -/** Writeable view to a ledger, for applying a transaction. - - This refinement of ReadView provides an interface where - the SLE can be "checked out" for modifications and put - back in an updated or removed state. Also added is an - interface to provide contextual information necessary - to calculate the results of transaction processing, - including the metadata if the view is later applied to - the parent (using an interface in the derived class). - The context info also includes values from the base - ledger such as sequence number and the network time. - - This allows implementations to journal changes made to - the state items in a ledger, with the option to apply - those changes to the base or discard the changes without - affecting the base. - - Typical usage is to call read() for non-mutating - operations. - - For mutating operations the sequence is as follows: - - // Add a new value - v.insert(sle); - - // Check out a value for modification - sle = v.peek(k); - - // Indicate that changes were made - v.update(sle) - - // Or, erase the value - v.erase(sle) - - The invariant is that insert, update, and erase may not - be called with any SLE which belongs to different view. -*/ +/** + * Writeable view to a ledger, for applying a transaction. + * + * This refinement of ReadView provides an interface where + * the SLE can be "checked out" for modifications and put + * back in an updated or removed state. Also added is an + * interface to provide contextual information necessary + * to calculate the results of transaction processing, + * including the metadata if the view is later applied to + * the parent (using an interface in the derived class). + * The context info also includes values from the base + * ledger such as sequence number and the network time. + * + * This allows implementations to journal changes made to + * the state items in a ledger, with the option to apply + * those changes to the base or discard the changes without + * affecting the base. + * + * Typical usage is to call read() for non-mutating + * operations. + * + * For mutating operations the sequence is as follows: + * + * // Add a new value + * v.insert(sle); + * + * // Check out a value for modification + * sle = v.peek(k); + * + * // Indicate that changes were made + * v.update(sle) + * + * // Or, erase the value + * v.erase(sle) + * + * The invariant is that insert, update, and erase may not + * be called with any SLE which belongs to different view. + */ class ApplyView : public ReadView { private: - /** Add an entry to a directory using the specified insert strategy */ + /** + * Add an entry to a directory using the specified insert strategy + */ std::optional dirAdd( bool preserveOrder, @@ -141,84 +146,89 @@ private: public: ApplyView() = default; - /** Returns the tx apply flags. - - Flags can affect the outcome of transaction - processing. For example, transactions applied - to an open ledger generate "local" failures, - while transactions applied to the consensus - ledger produce hard failures (and claim a fee). - */ + /** + * Returns the tx apply flags. + * + * Flags can affect the outcome of transaction + * processing. For example, transactions applied + * to an open ledger generate "local" failures, + * while transactions applied to the consensus + * ledger produce hard failures (and claim a fee). + */ [[nodiscard]] virtual ApplyFlags flags() const = 0; - /** Prepare to modify the SLE associated with key. - - Effects: - - Gives the caller ownership of a modifiable - SLE associated with the specified key. - - The returned SLE may be used in a subsequent - call to erase or update. - - The SLE must not be passed to any other ApplyView. - - @return `nullptr` if the key is not present - */ + /** + * Prepare to modify the SLE associated with key. + * + * Effects: + * + * Gives the caller ownership of a modifiable + * SLE associated with the specified key. + * + * The returned SLE may be used in a subsequent + * call to erase or update. + * + * The SLE must not be passed to any other ApplyView. + * + * @return `nullptr` if the key is not present + */ virtual SLE::pointer peek(Keylet const& k) = 0; - /** Remove a peeked SLE. - - Requirements: - - `sle` was obtained from prior call to peek() - on this instance of the RawView. - - Effects: - - The key is no longer associated with the SLE. - */ + /** + * Remove a peeked SLE. + * + * Requirements: + * + * `sle` was obtained from prior call to peek() + * on this instance of the RawView. + * + * Effects: + * + * The key is no longer associated with the SLE. + */ virtual void erase(SLE::ref sle) = 0; - /** Insert a new state SLE - - Requirements: - - `sle` was not obtained from any calls to - peek() on any instances of RawView. - - The SLE's key must not already exist. - - Effects: - - The key in the state map is associated - with the SLE. - - The RawView acquires ownership of the shared_ptr. - - @note The key is taken from the SLE - */ + /** + * Insert a new state SLE + * + * Requirements: + * + * `sle` was not obtained from any calls to + * peek() on any instances of RawView. + * + * The SLE's key must not already exist. + * + * Effects: + * + * The key in the state map is associated + * with the SLE. + * + * The RawView acquires ownership of the shared_ptr. + * + * @note The key is taken from the SLE + */ virtual void insert(SLE::ref sle) = 0; - /** Indicate changes to a peeked SLE - - Requirements: - - The SLE's key must exist. - - `sle` was obtained from prior call to peek() - on this instance of the RawView. - - Effects: - - The SLE is updated - - @note The key is taken from the SLE - */ + /** + * Indicate changes to a peeked SLE + * + * Requirements: + * + * The SLE's key must exist. + * + * `sle` was obtained from prior call to peek() + * on this instance of the RawView. + * + * Effects: + * + * The SLE is updated + * + * @note The key is taken from the SLE + */ /** @{ */ virtual void update(SLE::ref sle) = 0; @@ -248,7 +258,8 @@ public: XRPL_ASSERT(amount.holds(), "creditHookMPT: amount is for MPTIssue"); } - /** Facilitate tracking of MPT sold by an issuer owning MPT sell offer. + /** + * Facilitate tracking of MPT sold by an issuer owning MPT sell offer. * Unlike IOU, MPT doesn't have bi-directional relationship with an issuer, * where a trustline limits an amount that can be issued to a holder. * Consequently, the credit step (last MPTEndpointStep or @@ -288,27 +299,28 @@ public: // Called when the owner count changes // This is required to support PaymentSandbox virtual void - adjustOwnerCountHook(AccountID const& account, std::uint32_t cur, std::uint32_t next) + adjustOwnerCountHook(AccountID const& account, OwnerCounts const& cur, OwnerCounts const& next) { } - /** Append an entry to a directory - - Entries in the directory will be stored in order of insertion, i.e. new - entries will always be added at the tail end of the last page. - - @param directory the base of the directory - @param key the entry to insert - @param describe callback to add required entries to a new page - - @return a \c std::optional which, if insertion was successful, - will contain the page number in which the item was stored. - - @note this function may create a page (including a root page), if no - page with space is available. This function will only fail if the - page counter exceeds the protocol-defined maximum number of - allowable pages. - */ + /** + * Append an entry to a directory + * + * Entries in the directory will be stored in order of insertion, i.e. new + * entries will always be added at the tail end of the last page. + * + * @param directory the base of the directory + * @param key the entry to insert + * @param describe callback to add required entries to a new page + * + * @return a @c std::optional which, if insertion was successful, + * will contain the page number in which the item was stored. + * + * @note this function may create a page (including a root page), if no + * page with space is available. This function will only fail if the + * page counter exceeds the protocol-defined maximum number of + * allowable pages. + */ /** @{ */ std::optional dirAppend( @@ -331,23 +343,24 @@ public: } /** @} */ - /** Insert an entry to a directory - - Entries in the directory will be stored in a semi-random order, but - each page will be maintained in sorted order. - - @param directory the base of the directory - @param key the entry to insert - @param describe callback to add required entries to a new page - - @return a \c std::optional which, if insertion was successful, - will contain the page number in which the item was stored. - - @note this function may create a page (including a root page), if no - page with space is available.this function will only fail if the - page counter exceeds the protocol-defined maximum number of - allowable pages. - */ + /** + * Insert an entry to a directory + * + * Entries in the directory will be stored in a semi-random order, but + * each page will be maintained in sorted order. + * + * @param directory the base of the directory + * @param key the entry to insert + * @param describe callback to add required entries to a new page + * + * @return a @c std::optional which, if insertion was successful, + * will contain the page number in which the item was stored. + * + * @note this function may create a page (including a root page), if no + * page with space is available.this function will only fail if the + * page counter exceeds the protocol-defined maximum number of + * allowable pages. + */ /** @{ */ std::optional dirInsert( @@ -368,21 +381,22 @@ public: } /** @} */ - /** Remove an entry from a directory - - @param directory the base of the directory - @param page the page number for this page - @param key the entry to remove - @param keepRoot if deleting the last entry, don't - delete the root page (i.e. the directory itself). - - @return \c true if the entry was found and deleted and - \c false otherwise. - - @note This function will remove zero or more pages from the directory; - the root page will not be deleted even if it is empty, unless - \p keepRoot is not set and the directory is empty. - */ + /** + * Remove an entry from a directory + * + * @param directory the base of the directory + * @param page the page number for this page + * @param key the entry to remove + * @param keepRoot if deleting the last entry, don't + * delete the root page (i.e. the directory itself). + * + * @return @c true if the entry was found and deleted and + * @c false otherwise. + * + * @note This function will remove zero or more pages from the directory; + * the root page will not be deleted even if it is empty, unless + * \p keepRoot is not set and the directory is empty. + */ /** @{ */ bool dirRemove(Keylet const& directory, std::uint64_t page, uint256 const& key, bool keepRoot); @@ -394,29 +408,51 @@ public: } /** @} */ - /** Remove the specified directory, invoking the callback for every node. */ + /** + * Remove the specified directory, invoking the callback for every node. + */ bool dirDelete(Keylet const& directory, std::function const&); - /** Remove the specified directory, if it is empty. - - @param directory the identifier of the directory node to be deleted - @return \c true if the directory was found and was successfully deleted - \c false otherwise. - - @note The function should only be called with the root entry (i.e. with - the first page) of a directory. - */ + /** + * Remove the specified directory, if it is empty. + * + * @param directory the identifier of the directory node to be deleted + * @return @c true if the directory was found and was successfully deleted + * @c false otherwise. + * + * @note The function should only be called with the root entry (i.e. with + * the first page) of a directory. + */ bool emptyDirDelete(Keylet const& directory); }; -namespace directory { -/** Helper functions for managing low-level directory operations. - These are not part of the ApplyView interface. +/** + * Bundles the mutable ledger view and the transaction being applied. + * + * Passed together to avoid threading two separate parameters through every + * helper that needs both the view (for state reads/writes) and the + * transaction (for field inspection and metadata). + * + * Both members are non-owning references; the caller is responsible for + * ensuring that the referenced objects outlive the ApplyViewContext. + * + * TODO: replace with ApplyContext after it's untangled with xrpl/tx + */ +struct ApplyViewContext +{ + ApplyView& view; + STTx const& tx; +}; - Don't use them unless you really, really know what you're doing. - Instead use dirAdd, dirInsert, etc. +namespace directory { +/** + * Helper functions for managing low-level directory operations. + * These are not part of the ApplyView interface. + * + * Don't use them unless you really, really know what you're doing. + * Instead use dirAdd, dirInsert, etc. */ std::uint64_t diff --git a/include/xrpl/ledger/ApplyViewImpl.h b/include/xrpl/ledger/ApplyViewImpl.h index 9a3734a8ca..630153f90a 100644 --- a/include/xrpl/ledger/ApplyViewImpl.h +++ b/include/xrpl/ledger/ApplyViewImpl.h @@ -18,12 +18,13 @@ namespace xrpl { -/** Editable, discardable view that can build metadata for one tx. - - Iteration of the tx map is delegated to the base. - - @note Presented as ApplyView to clients. -*/ +/** + * Editable, discardable view that can build metadata for one tx. + * + * Iteration of the tx map is delegated to the base. + * + * @note Presented as ApplyView to clients. + */ class ApplyViewImpl final : public detail::ApplyViewBase { public: @@ -37,12 +38,13 @@ public: ApplyViewImpl(ApplyViewImpl&&) = default; ApplyViewImpl(ReadView const* base, ApplyFlags flags); - /** Apply the transaction. - - After a call to `apply`, the only valid - operation on this object is to call the - destructor. - */ + /** + * Apply the transaction. + * + * After a call to `apply`, the only valid + * operation on this object is to call the + * destructor. + */ std::optional apply( OpenView& to, @@ -52,25 +54,28 @@ public: bool isDryRun, beast::Journal j); - /** Set the amount of currency delivered. - - This value is used when generating metadata - for payments, to set the DeliveredAmount field. - If the amount is not specified, the field is - excluded from the resulting metadata. - */ + /** + * Set the amount of currency delivered. + * + * This value is used when generating metadata + * for payments, to set the DeliveredAmount field. + * If the amount is not specified, the field is + * excluded from the resulting metadata. + */ void deliver(STAmount const& amount) { deliver_ = amount; } - /** Get the number of modified entries + /** + * Get the number of modified entries */ std::size_t size(); - /** Visit modified entries + /** + * Visit modified entries */ void visit( diff --git a/include/xrpl/ledger/CachedView.h b/include/xrpl/ledger/CachedView.h index 1da3a67563..b9e2cf8d66 100644 --- a/include/xrpl/ledger/CachedView.h +++ b/include/xrpl/ledger/CachedView.h @@ -133,10 +133,11 @@ public: } // namespace detail -/** Wraps a DigestAwareReadView to provide caching. - - @tparam Base A subclass of DigestAwareReadView -*/ +/** + * Wraps a DigestAwareReadView to provide caching. + * + * @tparam Base A subclass of DigestAwareReadView + */ template class CachedView : public detail::CachedViewImpl { @@ -158,10 +159,11 @@ public: { } - /** Returns the base type. - - @note This breaks encapsulation and bypasses the cache. - */ + /** + * Returns the base type. + * + * @note This breaks encapsulation and bypasses the cache. + */ std::shared_ptr const& base() const { diff --git a/include/xrpl/ledger/CanonicalTXSet.h b/include/xrpl/ledger/CanonicalTXSet.h index f8349dfab6..11aadf4e92 100644 --- a/include/xrpl/ledger/CanonicalTXSet.h +++ b/include/xrpl/ledger/CanonicalTXSet.h @@ -13,13 +13,13 @@ namespace xrpl { -/** Holds transactions which were deferred to the next pass of consensus. - - "Canonical" refers to the order in which transactions are applied. - - - Puts transactions from the same account in SeqProxy order - -*/ +/** + * Holds transactions which were deferred to the next pass of consensus. + * + * "Canonical" refers to the order in which transactions are applied. + * + * - Puts transactions from the same account in SeqProxy order + */ // VFALCO TODO rename to SortedTxSet class CanonicalTXSet : public CountedObject { diff --git a/include/xrpl/ledger/Dir.h b/include/xrpl/ledger/Dir.h index 05df887d8b..233719cdeb 100644 --- a/include/xrpl/ledger/Dir.h +++ b/include/xrpl/ledger/Dir.h @@ -13,18 +13,19 @@ namespace xrpl { -/** A class that simplifies iterating ledger directory pages - - The Dir class provides a forward iterator for walking through - the uint256 values contained in ledger directories. - - The Dir class also allows accelerated directory walking by - stepping directly from one page to the next using the next_page() - member function. - - As of July 2024, the Dir class is only being used with NFTokenOffer - directories and for unit tests. -*/ +/** + * A class that simplifies iterating ledger directory pages + * + * The Dir class provides a forward iterator for walking through + * the uint256 values contained in ledger directories. + * + * The Dir class also allows accelerated directory walking by + * stepping directly from one page to the next using the next_page() + * member function. + * + * As of July 2024, the Dir class is only being used with NFTokenOffer + * directories and for unit tests. + */ class Dir { private: diff --git a/include/xrpl/ledger/Ledger.h b/include/xrpl/ledger/Ledger.h index 3453389a5e..e1dd2c422e 100644 --- a/include/xrpl/ledger/Ledger.h +++ b/include/xrpl/ledger/Ledger.h @@ -42,32 +42,33 @@ struct CreateGenesisT }; extern CreateGenesisT const kCreateGenesis; -/** Holds a ledger. - - The ledger is composed of two SHAMaps. The state map holds all of the - ledger entries such as account roots and order books. The tx map holds - all of the transactions and associated metadata that made it into that - particular ledger. Most of the operations on a ledger are concerned - with the state map. - - This can hold just the header, a partial set of data, or the entire set - of data. It all depends on what is in the corresponding SHAMap entry. - Various functions are provided to populate or depopulate the caches that - the object holds references to. - - Ledgers are constructed as either mutable or immutable. - - 1) If you are the sole owner of a mutable ledger, you can do whatever you - want with no need for locks. - - 2) If you have an immutable ledger, you cannot ever change it, so no need - for locks. - - 3) Mutable ledgers cannot be shared. - - @note Presented to clients as ReadView - @note Calls virtuals in the constructor, so marked as final -*/ +/** + * Holds a ledger. + * + * The ledger is composed of two SHAMaps. The state map holds all of the + * ledger entries such as account roots and order books. The tx map holds + * all of the transactions and associated metadata that made it into that + * particular ledger. Most of the operations on a ledger are concerned + * with the state map. + * + * This can hold just the header, a partial set of data, or the entire set + * of data. It all depends on what is in the corresponding SHAMap entry. + * Various functions are provided to populate or depopulate the caches that + * the object holds references to. + * + * Ledgers are constructed as either mutable or immutable. + * + * 1) If you are the sole owner of a mutable ledger, you can do whatever you + * want with no need for locks. + * + * 2) If you have an immutable ledger, you cannot ever change it, so no need + * for locks. + * + * 3) Mutable ledgers cannot be shared. + * + * @note Presented to clients as ReadView + * @note Calls virtuals in the constructor, so marked as final + */ class Ledger final : public std::enable_shared_from_this, public DigestAwareReadView, public TxsRawView, @@ -82,20 +83,21 @@ public: Ledger& operator=(Ledger&&) = delete; - /** Create the Genesis ledger. - - The Genesis ledger contains a single account whose - AccountID is generated with a Generator using the seed - computed from the string "masterpassphrase" and ordinal - zero. - - The account has an XRP balance equal to the total amount - of XRP in the system. No more XRP than the amount which - starts in this account can ever exist, with amounts - used to pay fees being destroyed. - - Amendments specified are enabled in the genesis ledger - */ + /** + * Create the Genesis ledger. + * + * The Genesis ledger contains a single account whose + * AccountID is generated with a Generator using the seed + * computed from the string "masterpassphrase" and ordinal + * zero. + * + * The account has an XRP balance equal to the total amount + * of XRP in the system. No more XRP than the amount which + * starts in this account can ever exist, with amounts + * used to pay fees being destroyed. + * + * Amendments specified are enabled in the genesis ledger + */ Ledger( CreateGenesisT, Rules rules, @@ -105,13 +107,14 @@ public: Ledger(LedgerHeader const& info, Rules rules, Family& family); - /** Used for ledgers loaded from JSON files - - @param acquire If true, acquires the ledger if not found locally - - @note The fees parameter provides default values, but setup() may - override them from the ledger state if fee-related SLEs exist. - */ + /** + * Used for ledgers loaded from JSON files + * + * @param acquire If true, acquires the ledger if not found locally + * + * @note The fees parameter provides default values, but setup() may + * override them from the ledger state if fee-related SLEs exist. + */ Ledger( LedgerHeader const& info, bool& loaded, @@ -121,12 +124,13 @@ public: Family& family, beast::Journal j); - /** Create a new ledger following a previous ledger - - The ledger will have the sequence number that - follows previous, and have - parentCloseTime == previous.closeTime. - */ + /** + * Create a new ledger following a previous ledger + * + * The ledger will have the sequence number that + * follows previous, and have + * parentCloseTime == previous.closeTime. + */ Ledger(Ledger const& previous, NetClock::time_point closeTime); // used for database ledgers @@ -369,11 +373,15 @@ public: void updateNegativeUNL(); - /** Returns true if the ledger is a flag ledger */ + /** + * Returns true if the ledger is a flag ledger + */ bool isFlagLedger() const; - /** Returns true if the ledger directly precedes a flag ledger */ + /** + * Returns true if the ledger directly precedes a flag ledger + */ bool isVotingLedger() const; @@ -387,23 +395,25 @@ private: bool setup(); - /** @brief Deserialize a SHAMapItem containing a single STTx. + /** + * @brief Deserialize a SHAMapItem containing a single STTx. * * @param item The SHAMapItem to deserialize. * @return A shared pointer to the deserialized transaction. - * @throw May throw on deserialization error. + * @throws May throw on deserialization error. */ static std::shared_ptr deserializeTx(SHAMapItem const& item); - /** @brief Deserialize a SHAMapItem containing STTx + STObject metadata. + /** + * @brief Deserialize a SHAMapItem containing STTx + STObject metadata. * * The SHAMapItem must contain two variable length serialization objects. * * @param item The SHAMapItem to deserialize. * @return A pair containing shared pointers to the deserialized transaction * and metadata. - * @throw May throw on deserialization error. + * @throws May throw on deserialization error. */ static std::pair, std::shared_ptr> deserializeTxPlusMeta(SHAMapItem const& item); @@ -425,7 +435,9 @@ private: beast::Journal j_; }; -/** A ledger wrapped in a CachedView. */ +/** + * A ledger wrapped in a CachedView. + */ using CachedLedger = CachedView; } // namespace xrpl diff --git a/include/xrpl/ledger/LedgerTiming.h b/include/xrpl/ledger/LedgerTiming.h index a97e229046..77254a434b 100644 --- a/include/xrpl/ledger/LedgerTiming.h +++ b/include/xrpl/ledger/LedgerTiming.h @@ -8,11 +8,12 @@ namespace xrpl { -/** Possible ledger close time resolutions. - - Values should not be duplicated. - @see getNextLedgerTimeResolution -*/ +/** + * Possible ledger close time resolutions. + * + * Values should not be duplicated. + * @see getNextLedgerTimeResolution + */ constexpr std::chrono::seconds kLedgerPossibleTimeResolutions[] = { std::chrono::seconds{10}, std::chrono::seconds{20}, @@ -21,41 +22,50 @@ constexpr std::chrono::seconds kLedgerPossibleTimeResolutions[] = { std::chrono::seconds{90}, std::chrono::seconds{120}}; -//! Initial resolution of ledger close time. +/** + * Initial resolution of ledger close time. + */ constexpr auto kLedgerDefaultTimeResolution = kLedgerPossibleTimeResolutions[2]; -//! Close time resolution in genesis ledger +/** + * Close time resolution in genesis ledger + */ constexpr auto kLedgerGenesisTimeResolution = kLedgerPossibleTimeResolutions[0]; -//! How often we increase the close time resolution (in numbers of ledgers) +/** + * How often we increase the close time resolution (in numbers of ledgers) + */ constexpr auto kIncreaseLedgerTimeResolutionEvery = 8; -//! How often we decrease the close time resolution (in numbers of ledgers) +/** + * How often we decrease the close time resolution (in numbers of ledgers) + */ constexpr auto kDecreaseLedgerTimeResolutionEvery = 1; -/** Calculates the close time resolution for the specified ledger. - - The XRPL protocol uses binning to represent time intervals using only one - timestamp. This allows servers to derive a common time for the next ledger, - without the need for perfectly synchronized clocks. - The time resolution (i.e. the size of the intervals) is adjusted dynamically - based on what happened in the last ledger, to try to avoid disagreements. - - @param previousResolution the resolution used for the prior ledger - @param previousAgree whether consensus agreed on the close time of the prior - ledger - @param ledgerSeq the sequence number of the new ledger - - @pre previousResolution must be a valid bin - from @ref kLedgerPossibleTimeResolutions - - @tparam Rep Type representing number of ticks in std::chrono::duration - @tparam Period An std::ratio representing tick period in - std::chrono::duration - @tparam Seq Unsigned integer-like type corresponding to the ledger sequence - number. It should be comparable to 0 and support modular - division. Built-in and tagged_integers are supported. -*/ +/** + * Calculates the close time resolution for the specified ledger. + * + * The XRPL protocol uses binning to represent time intervals using only one + * timestamp. This allows servers to derive a common time for the next ledger, + * without the need for perfectly synchronized clocks. + * The time resolution (i.e. the size of the intervals) is adjusted dynamically + * based on what happened in the last ledger, to try to avoid disagreements. + * + * @tparam Rep Type representing number of ticks in std::chrono::duration + * @tparam Period An std::ratio representing tick period in + * std::chrono::duration + * @tparam Seq Unsigned integer-like type corresponding to the ledger sequence + * number. It should be comparable to 0 and support modular + * division. Built-in and tagged_integers are supported. + * + * @param previousResolution the resolution used for the prior ledger + * @param previousAgree whether consensus agreed on the close time of the prior + * ledger + * @param ledgerSeq the sequence number of the new ledger + * + * @pre previousResolution must be a valid bin + * from @ref kLedgerPossibleTimeResolutions + */ template std::chrono::duration getNextLedgerTimeResolution( @@ -98,13 +108,14 @@ getNextLedgerTimeResolution( return previousResolution; } -/** Calculates the close time for a ledger, given a close time resolution. - - @param closeTime The time to be rounded - @param closeResolution The resolution - @return @b closeTime rounded to the nearest multiple of @b closeResolution. - Rounds up if @b closeTime is midway between multiples of @b closeResolution. -*/ +/** + * Calculates the close time for a ledger, given a close time resolution. + * + * @param closeTime The time to be rounded + * @param closeResolution The resolution + * @return @b closeTime rounded to the nearest multiple of @b closeResolution. + * Rounds up if @b closeTime is midway between multiples of @b closeResolution. + */ template std::chrono::time_point roundCloseTime( @@ -119,15 +130,16 @@ roundCloseTime( return closeTime - (closeTime.time_since_epoch() % closeResolution); } -/** Calculate the effective ledger close time - - After adjusting the ledger close time based on the current resolution, also - ensure it is sufficiently separated from the prior close time. - - @param closeTime The raw ledger close time - @param resolution The current close time resolution - @param priorCloseTime The close time of the prior ledger -*/ +/** + * Calculate the effective ledger close time + * + * After adjusting the ledger close time based on the current resolution, also + * ensure it is sufficiently separated from the prior close time. + * + * @param closeTime The raw ledger close time + * @param resolution The current close time resolution + * @param priorCloseTime The close time of the prior ledger + */ template std::chrono::time_point effCloseTime( diff --git a/include/xrpl/ledger/OpenView.h b/include/xrpl/ledger/OpenView.h index 875909715c..3f8e950b02 100644 --- a/include/xrpl/ledger/OpenView.h +++ b/include/xrpl/ledger/OpenView.h @@ -25,21 +25,23 @@ namespace xrpl { -/** Open ledger construction tag. - - Views constructed with this tag will have the - rules of open ledgers applied during transaction - processing. +/** + * Open ledger construction tag. + * + * Views constructed with this tag will have the + * rules of open ledgers applied during transaction + * processing. */ inline constexpr struct OpenLedgerT { explicit constexpr OpenLedgerT() = default; } kOpenLedger{}; -/** Batch view construction tag. - - Views constructed with this tag are part of a stack of views - used during batch transaction application. +/** + * Batch view construction tag. + * + * Views constructed with this tag are part of a stack of views + * used during batch transaction application. */ inline constexpr struct BatchViewT { @@ -48,10 +50,11 @@ inline constexpr struct BatchViewT //------------------------------------------------------------------------------ -/** Writable ledger view that accumulates state and tx changes. - - @note Presented as ReadView to clients. -*/ +/** + * Writable ledger view that accumulates state and tx changes. + * + * @note Presented as ReadView to clients. + */ class OpenView final : public ReadView, public TxsRawView { private: @@ -95,7 +98,9 @@ private: detail::RawStateTable items_; std::shared_ptr hold_; - /// In batch mode, the number of transactions already executed. + /** + * In batch mode, the number of transactions already executed. + */ std::size_t baseTxCount_ = 0; bool open_ = true; @@ -109,40 +114,42 @@ public: OpenView(OpenView&&) = default; - /** Construct a shallow copy. - - Effects: - - Creates a new object with a copy of - the modification state table. - - The objects managed by shared pointers are - not duplicated but shared between instances. - Since the SLEs are immutable, calls on the - RawView interface cannot break invariants. - */ + /** + * Construct a shallow copy. + * + * Effects: + * + * Creates a new object with a copy of + * the modification state table. + * + * The objects managed by shared pointers are + * not duplicated but shared between instances. + * Since the SLEs are immutable, calls on the + * RawView interface cannot break invariants. + */ OpenView(OpenView const&); - /** Construct an open ledger view. - - Effects: - - The sequence number is set to the - sequence number of parent plus one. - - The parentCloseTime is set to the - closeTime of parent. - - If `hold` is not nullptr, retains - ownership of a copy of `hold` until - the MetaView is destroyed. - - Calls to rules() will return the - rules provided on construction. - - The tx list starts empty and will contain - all newly inserted tx. - */ + /** + * Construct an open ledger view. + * + * Effects: + * + * The sequence number is set to the + * sequence number of parent plus one. + * + * The parentCloseTime is set to the + * closeTime of parent. + * + * If `hold` is not nullptr, retains + * ownership of a copy of `hold` until + * the MetaView is destroyed. + * + * Calls to rules() will return the + * rules provided on construction. + * + * The tx list starts empty and will contain + * all newly inserted tx. + */ OpenView( OpenLedgerT, ReadView const* base, @@ -159,35 +166,41 @@ public: baseTxCount_ = base.txCount(); } - /** Construct a new last closed ledger. - - Effects: - - The LedgerHeader is copied from the base. - - The rules are inherited from the base. - - The tx list starts empty and will contain - all newly inserted tx. - */ + /** + * Construct a new last closed ledger. + * + * Effects: + * + * The LedgerHeader is copied from the base. + * + * The rules are inherited from the base. + * + * The tx list starts empty and will contain + * all newly inserted tx. + */ OpenView(ReadView const* base, std::shared_ptr hold = nullptr); - /** Returns true if this reflects an open ledger. */ + /** + * Returns true if this reflects an open ledger. + */ bool open() const override { return open_; } - /** Return the number of tx inserted since creation. - - This is used to set the "apply ordinal" - when calculating transaction metadata. - */ + /** + * Return the number of tx inserted since creation. + * + * This is used to set the "apply ordinal" + * when calculating transaction metadata. + */ std::size_t txCount() const; - /** Apply changes. */ + /** + * Apply changes. + */ void apply(TxsRawView& to) const; diff --git a/include/xrpl/ledger/OrderBookDB.h b/include/xrpl/ledger/OrderBookDB.h index a44183900c..96dc94b1f4 100644 --- a/include/xrpl/ledger/OrderBookDB.h +++ b/include/xrpl/ledger/OrderBookDB.h @@ -14,85 +14,92 @@ namespace xrpl { -/** Tracks order books in the ledger. - - This interface provides access to order book information, including: - - Which order books exist in the ledger - - Querying order books by issue - - Managing order book subscriptions - - The order book database is updated as ledgers are accepted and provides - efficient lookup of order book information for pathfinding and client - subscriptions. -*/ +/** + * Tracks order books in the ledger. + * + * This interface provides access to order book information, including: + * - Which order books exist in the ledger + * - Querying order books by issue + * - Managing order book subscriptions + * + * The order book database is updated as ledgers are accepted and provides + * efficient lookup of order book information for pathfinding and client + * subscriptions. + */ class OrderBookDB { public: virtual ~OrderBookDB() = default; - /** Initialize or update the order book database with a new ledger. - - This method should be called when a new ledger is accepted to update - the order book database with the current state of all order books. - - @param ledger The ledger to scan for order books - */ + /** + * Initialize or update the order book database with a new ledger. + * + * This method should be called when a new ledger is accepted to update + * the order book database with the current state of all order books. + * + * @param ledger The ledger to scan for order books + */ virtual void setup(std::shared_ptr const& ledger) = 0; - /** Add an order book to track. - - @param book The order book to add - */ + /** + * Add an order book to track. + * + * @param book The order book to add + */ virtual void addOrderBook(Book const& book) = 0; - /** Get all order books that want a specific issue. - - Returns a list of all order books where the taker pays the specified - issue. This is useful for pathfinding to find all possible next hops - from a given currency. - - @param asset The asset to search for - @param domain Optional domain restriction for the order book - @return Vector of books that want this issue - */ + /** + * Get all order books that want a specific issue. + * + * Returns a list of all order books where the taker pays the specified + * issue. This is useful for pathfinding to find all possible next hops + * from a given currency. + * + * @param asset The asset to search for + * @param domain Optional domain restriction for the order book + * @return Vector of books that want this issue + */ virtual std::vector getBooksByTakerPays(Asset const& asset, std::optional const& domain = std::nullopt) = 0; - /** Get the count of order books that want a specific issue. - - @param asset The asset to search for - @param domain Optional domain restriction for the order book - @return Number of books that want this issue - */ + /** + * Get the count of order books that want a specific issue. + * + * @param asset The asset to search for + * @param domain Optional domain restriction for the order book + * @return Number of books that want this issue + */ virtual int getBookSize(Asset const& asset, std::optional const& domain = std::nullopt) = 0; - /** Check if an order book to XRP exists for the given issue. - - @param asset The asset to check - @param domain Optional domain restriction for the order book - @return true if a book from this issue to XRP exists - */ + /** + * Check if an order book to XRP exists for the given issue. + * + * @param asset The asset to check + * @param domain Optional domain restriction for the order book + * @return true if a book from this issue to XRP exists + */ virtual bool isBookToXRP(Asset const& asset, std::optional const& domain = std::nullopt) = 0; }; -/** Extract the set of books affected by a transaction. +/** + * Extract the set of books affected by a transaction. * - * Walks the transaction's metadata nodes and collects every order book - * whose offers were created, modified, or deleted. Used by NetworkOPs to - * fan transaction notifications out to book subscribers. + * Walks the transaction's metadata nodes and collects every order book + * whose offers were created, modified, or deleted. Used by NetworkOPs to + * fan transaction notifications out to book subscribers. * - * @param alTx The accepted ledger transaction to inspect. - * @param j Journal used to log per-node parsing failures. Inspecting an - * offer node can throw if a required field is missing; in that - * case the bad node is skipped and a warn-level message is - * emitted via @p j. Other affected books in the same transaction - * are still returned. - * @return The set of books whose offers were created, modified, or - * deleted. May be empty for non-offer transactions. + * @param alTx The accepted ledger transaction to inspect. + * @param j Journal used to log per-node parsing failures. Inspecting an + * offer node can throw if a required field is missing; in that + * case the bad node is skipped and a warn-level message is + * emitted via @p j. Other affected books in the same transaction + * are still returned. + * @return The set of books whose offers were created, modified, or + * deleted. May be empty for non-offer transactions. */ hash_set affectedBooks(AcceptedLedgerTx const& alTx, beast::Journal const& j); diff --git a/include/xrpl/ledger/OwnerCounts.h b/include/xrpl/ledger/OwnerCounts.h new file mode 100644 index 0000000000..74d60014b5 --- /dev/null +++ b/include/xrpl/ledger/OwnerCounts.h @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include // IWYU pragma: keep +#include + +#include +#include + +namespace xrpl { + +struct OwnerCounts +{ + std::uint32_t owner = 0; + std::uint32_t sponsored = 0; + std::uint32_t sponsoring = 0; + + OwnerCounts() = default; + OwnerCounts(SLE::const_ref sle) + : owner(sle->at(sfOwnerCount)) + , sponsored(sle->at(sfSponsoredOwnerCount)) + , sponsoring(sle->at(sfSponsoringOwnerCount)) + { + XRPL_ASSERT( + owner >= sponsored, + "xrpl::OwnerCounts : OwnerCount must be greater than or equal to " + "SponsoredOwnerCount"); + XRPL_ASSERT(sle->getType() == ltACCOUNT_ROOT, "xrpl::OwnerCounts : sle is AccountRoot"); + } + + [[nodiscard]] std::uint32_t + count() const + { + std::int64_t const x = static_cast(owner) - sponsored + sponsoring; + if (x < 0) + { + // LCOV_EXCL_START + UNREACHABLE("xrpl::OwnerCounts::count : count less than zero"); + return 0; + // LCOV_EXCL_STOP + } + + if (x > std::numeric_limits::max()) + return std::numeric_limits::max(); // LCOV_EXCL_LINE + return static_cast(x); + } + + auto + operator<=>(OwnerCounts const& o) const + { + if (auto cmp = count() <=> o.count(); cmp != 0) // NOLINT(modernize-use-nullptr) + return cmp; + if (auto cmp = owner <=> o.owner; cmp != 0) // NOLINT(modernize-use-nullptr) + return cmp; + if (auto cmp = sponsored <=> o.sponsored; cmp != 0) // NOLINT(modernize-use-nullptr) + return cmp; + return sponsoring <=> o.sponsoring; + } + + bool + operator==(OwnerCounts const& o) const + { + return this == &o || + (owner == o.owner && sponsored == o.sponsored && sponsoring == o.sponsoring); + } +}; + +} // namespace xrpl diff --git a/include/xrpl/ledger/PaymentSandbox.h b/include/xrpl/ledger/PaymentSandbox.h index 0117a962ff..e725bdd556 100644 --- a/include/xrpl/ledger/PaymentSandbox.h +++ b/include/xrpl/ledger/PaymentSandbox.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -107,12 +108,12 @@ public: issuerSelfDebitMPT(MPTIssue const& issue, std::uint64_t amount, std::int64_t origBalance); void - ownerCount(AccountID const& id, std::uint32_t cur, std::uint32_t next); + ownerCount(AccountID const& id, OwnerCounts const& cur, OwnerCounts const& next); // Get the adjusted owner count. Since DeferredCredits is meant to be used // in payments, and payments only decrease owner counts, return the max // remembered owner count. - [[nodiscard]] std::optional + [[nodiscard]] std::optional ownerCount(AccountID const& id) const; void @@ -124,25 +125,26 @@ private: std::map creditsIOU_; std::map creditsMPT_; - std::map ownerCounts_; + std::map ownerCounts_; }; } // namespace detail //------------------------------------------------------------------------------ -/** A wrapper which makes credits unavailable to balances. - - This is used for payments and pathfinding, so that consuming - liquidity from a path never causes portions of that path or - other paths to gain liquidity. - - The behavior of certain free functions in the ApplyView API - will change via the balanceHook and creditHook overrides - of PaymentSandbox. - - @note Presented as ApplyView to clients -*/ +/** + * A wrapper which makes credits unavailable to balances. + * + * This is used for payments and pathfinding, so that consuming + * liquidity from a path never causes portions of that path or + * other paths to gain liquidity. + * + * The behavior of certain free functions in the ApplyView API + * will change via the balanceHook and creditHook overrides + * of PaymentSandbox. + * + * @note Presented as ApplyView to clients + */ class PaymentSandbox final : public detail::ApplyViewBase { public: @@ -163,16 +165,17 @@ public: { } - /** Construct on top of existing PaymentSandbox. - - The changes are pushed to the parent when - apply() is called. - - @param parent A non-null pointer to the parent. - - @note A pointer is used to prevent confusion - with copy construction. - */ + /** + * Construct on top of existing PaymentSandbox. + * + * The changes are pushed to the parent when + * apply() is called. + * + * @param parent A non-null pointer to the parent. + * + * @note A pointer is used to prevent confusion + * with copy construction. + */ // VFALCO If we are constructing on top of a PaymentSandbox, // or a PaymentSandbox-derived class, we MUST go through // one of these constructors or invariants will be broken. @@ -218,17 +221,19 @@ public: override; void - adjustOwnerCountHook(AccountID const& account, std::uint32_t cur, std::uint32_t next) override; + adjustOwnerCountHook(AccountID const& account, OwnerCounts const& cur, OwnerCounts const& next) + override; - [[nodiscard]] std::uint32_t - ownerCountHook(AccountID const& account, std::uint32_t count) const override; + [[nodiscard]] OwnerCounts + ownerCountHook(AccountID const& account, OwnerCounts const& count) const override; - /** Apply changes to base view. - - `to` must contain contents identical to the parent - view passed upon construction, else undefined - behavior will result. - */ + /** + * Apply changes to base view. + * + * `to` must contain contents identical to the parent + * view passed upon construction, else undefined + * behavior will result. + */ /** @{ */ void apply(RawView& to); diff --git a/include/xrpl/ledger/PendingSaves.h b/include/xrpl/ledger/PendingSaves.h index a18292df68..723ae1aef1 100644 --- a/include/xrpl/ledger/PendingSaves.h +++ b/include/xrpl/ledger/PendingSaves.h @@ -8,12 +8,13 @@ namespace xrpl { -/** Keeps track of which ledgers haven't been fully saved. - - During the ledger building process this collection will keep - track of those ledgers that are being built but have not yet - been completely written. -*/ +/** + * Keeps track of which ledgers haven't been fully saved. + * + * During the ledger building process this collection will keep + * track of those ledgers that are being built but have not yet + * been completely written. + */ class PendingSaves { private: @@ -22,12 +23,13 @@ private: std::condition_variable await_; public: - /** Start working on a ledger - - This is called prior to updating the SQLite indexes. - - @return 'true' if work should be done - */ + /** + * Start working on a ledger + * + * This is called prior to updating the SQLite indexes. + * + * @return 'true' if work should be done + */ bool startWork(LedgerIndex seq) { @@ -45,12 +47,13 @@ public: return true; } - /** Finish working on a ledger - - This is called after updating the SQLite indexes. - The tracking of the work in progress is removed and - threads awaiting completion are notified. - */ + /** + * Finish working on a ledger + * + * This is called after updating the SQLite indexes. + * The tracking of the work in progress is removed and + * threads awaiting completion are notified. + */ void finishWork(LedgerIndex seq) { @@ -60,7 +63,9 @@ public: await_.notify_all(); } - /** Return `true` if a ledger is in the progress of being saved. */ + /** + * Return `true` if a ledger is in the progress of being saved. + */ bool pending(LedgerIndex seq) { @@ -68,14 +73,15 @@ public: return map_.contains(seq); } - /** Check if a ledger should be dispatched - - Called to determine whether work should be done or - dispatched. If work is already in progress and the - call is synchronous, wait for work to be completed. - - @return 'true' if work should be done or dispatched - */ + /** + * Check if a ledger should be dispatched + * + * Called to determine whether work should be done or + * dispatched. If work is already in progress and the + * call is synchronous, wait for work to be completed. + * + * @return 'true' if work should be done or dispatched + */ bool shouldWork(LedgerIndex seq, bool isSynchronous) { @@ -108,12 +114,13 @@ public: } while (true); } - /** Get a snapshot of the pending saves - - Each entry in the returned map corresponds to a ledger - that is in progress or dispatched. The boolean indicates - whether work is currently in progress. - */ + /** + * Get a snapshot of the pending saves + * + * Each entry in the returned map corresponds to a ledger + * that is in progress or dispatched. The boolean indicates + * whether work is currently in progress. + */ std::map getSnapshot() const { diff --git a/include/xrpl/ledger/RawView.h b/include/xrpl/ledger/RawView.h index ac2674226f..b94a7aab27 100644 --- a/include/xrpl/ledger/RawView.h +++ b/include/xrpl/ledger/RawView.h @@ -9,10 +9,11 @@ namespace xrpl { -/** Interface for ledger entry changes. - - Subclasses allow raw modification of ledger entries. -*/ +/** + * Interface for ledger entry changes. + * + * Subclasses allow raw modification of ledger entries. + */ class RawView { public: @@ -22,66 +23,72 @@ public: RawView& operator=(RawView const&) = delete; - /** Delete an existing state item. - - The SLE is provided so the implementation - can calculate metadata. - */ + /** + * Delete an existing state item. + * + * The SLE is provided so the implementation + * can calculate metadata. + */ virtual void rawErase(SLE::ref sle) = 0; - /** Unconditionally insert a state item. - - Requirements: - The key must not already exist. - - Effects: - - The key is associated with the SLE. - - @note The key is taken from the SLE - */ + /** + * Unconditionally insert a state item. + * + * Requirements: + * The key must not already exist. + * + * Effects: + * + * The key is associated with the SLE. + * + * @note The key is taken from the SLE + */ virtual void rawInsert(SLE::ref sle) = 0; - /** Unconditionally replace a state item. - - Requirements: - - The key must exist. - - Effects: - - The key is associated with the SLE. - - @note The key is taken from the SLE - */ + /** + * Unconditionally replace a state item. + * + * Requirements: + * + * The key must exist. + * + * Effects: + * + * The key is associated with the SLE. + * + * @note The key is taken from the SLE + */ virtual void rawReplace(SLE::ref sle) = 0; - /** Destroy XRP. - - This is used to pay for transaction fees. - */ + /** + * Destroy XRP. + * + * This is used to pay for transaction fees. + */ virtual void rawDestroyXRP(XRPAmount const& fee) = 0; }; //------------------------------------------------------------------------------ -/** Interface for changing ledger entries with transactions. - - Allows raw modification of ledger entries and insertion - of transactions into the transaction map. -*/ +/** + * Interface for changing ledger entries with transactions. + * + * Allows raw modification of ledger entries and insertion + * of transactions into the transaction map. + */ class TxsRawView : public RawView { public: - /** Add a transaction to the tx map. - - Closed ledgers must have metadata, - while open ledgers omit metadata. - */ + /** + * Add a transaction to the tx map. + * + * Closed ledgers must have metadata, + * while open ledgers omit metadata. + */ virtual void rawTxInsert( ReadView::key_type const& key, diff --git a/include/xrpl/ledger/ReadView.h b/include/xrpl/ledger/ReadView.h index 724533039b..d0010b6030 100644 --- a/include/xrpl/ledger/ReadView.h +++ b/include/xrpl/ledger/ReadView.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -13,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -28,12 +30,13 @@ namespace xrpl { //------------------------------------------------------------------------------ -/** A view into a ledger. - - This interface provides read access to state - and transaction items. There is no checkpointing - or calculation of metadata. -*/ +/** + * A view into a ledger. + * + * This interface provides read access to state + * and transaction items. There is no checkpointing + * or calculation of metadata. + */ class ReadView { public: @@ -84,72 +87,87 @@ public: { } - /** Returns information about the ledger. */ + /** + * Returns information about the ledger. + */ [[nodiscard]] virtual LedgerHeader const& header() const = 0; - /** Returns true if this reflects an open ledger. */ + /** + * Returns true if this reflects an open ledger. + */ [[nodiscard]] virtual bool open() const = 0; - /** Returns the close time of the previous ledger. */ + /** + * Returns the close time of the previous ledger. + */ [[nodiscard]] NetClock::time_point parentCloseTime() const { return header().parentCloseTime; } - /** Returns the sequence number of the base ledger. */ + /** + * Returns the sequence number of the base ledger. + */ [[nodiscard]] LedgerIndex seq() const { return header().seq; } - /** Returns the fees for the base ledger. */ + /** + * Returns the fees for the base ledger. + */ [[nodiscard]] virtual Fees const& fees() const = 0; - /** Returns the tx processing rules. */ + /** + * Returns the tx processing rules. + */ [[nodiscard]] virtual Rules const& rules() const = 0; - /** Determine if a state item exists. - - @note This can be more efficient than calling read. - - @return `true` if a SLE is associated with the - specified key. - */ + /** + * Determine if a state item exists. + * + * @note This can be more efficient than calling read. + * + * @return `true` if a SLE is associated with the + * specified key. + */ [[nodiscard]] virtual bool exists(Keylet const& k) const = 0; - /** Return the key of the next state item. - - This returns the key of the first state item - whose key is greater than the specified key. If - no such key is present, std::nullopt is returned. - - If `last` is engaged, returns std::nullopt when - the key returned would be outside the open - interval (key, last). - */ + /** + * Return the key of the next state item. + * + * This returns the key of the first state item + * whose key is greater than the specified key. If + * no such key is present, std::nullopt is returned. + * + * If `last` is engaged, returns std::nullopt when + * the key returned would be outside the open + * interval (key, last). + */ [[nodiscard]] virtual std::optional succ(key_type const& key, std::optional const& last = std::nullopt) const = 0; - /** Return the state item associated with a key. - - Effects: - If the key exists, gives the caller ownership - of the non-modifiable corresponding SLE. - - @note While the returned SLE is `const` from the - perspective of the caller, it can be changed - by other callers through raw operations. - - @return `nullptr` if the key is not present or - if the type does not match. - */ + /** + * Return the state item associated with a key. + * + * Effects: + * If the key exists, gives the caller ownership + * of the non-modifiable corresponding SLE. + * + * @note While the returned SLE is `const` from the + * perspective of the caller, it can be changed + * by other callers through raw operations. + * + * @return `nullptr` if the key is not present or + * if the type does not match. + */ [[nodiscard]] virtual SLE::const_pointer read(Keylet const& k) const = 0; @@ -189,8 +207,8 @@ public: // changes that accounts make during a payment. `ownerCountHook` adjusts the // ownerCount so it returns the max value of the ownerCount so far. // This is required to support PaymentSandbox. - [[nodiscard]] virtual std::uint32_t - ownerCountHook(AccountID const& account, std::uint32_t count) const + [[nodiscard]] virtual OwnerCounts + ownerCountHook(AccountID const& account, OwnerCounts const& count) const { return count; } @@ -215,22 +233,24 @@ public: [[nodiscard]] virtual std::unique_ptr txsEnd() const = 0; - /** Returns `true` if a tx exists in the tx map. - - A tx exists in the map if it is part of the - base ledger, or if it is a newly inserted tx. - */ + /** + * Returns `true` if a tx exists in the tx map. + * + * A tx exists in the map if it is part of the + * base ledger, or if it is a newly inserted tx. + */ [[nodiscard]] virtual bool txExists(key_type const& key) const = 0; - /** Read a transaction from the tx map. - - If the view represents an open ledger, - the metadata object will be empty. - - @return A pair of nullptr if the - key is not found in the tx map. - */ + /** + * Read a transaction from the tx map. + * + * If the view represents an open ledger, + * the metadata object will be empty. + * + * @return A pair of nullptr if the + * key is not found in the tx map. + */ [[nodiscard]] virtual tx_type txRead(key_type const& key) const = 0; @@ -238,11 +258,12 @@ public: // Memberspaces // - /** Iterable range of ledger state items. - - @note Visiting each state entry in the ledger can - become quite expensive as the ledger grows. - */ + /** + * Iterable range of ledger state items. + * + * @note Visiting each state entry in the ledger can + * become quite expensive as the ledger grows. + */ SlesType sles; // The range of transactions @@ -251,7 +272,9 @@ public: //------------------------------------------------------------------------------ -/** ReadView that associates keys with digests. */ +/** + * ReadView that associates keys with digests. + */ class DigestAwareReadView : public ReadView { public: @@ -260,10 +283,11 @@ public: DigestAwareReadView() = default; DigestAwareReadView(DigestAwareReadView const&) = default; - /** Return the digest associated with the key. - - @return std::nullopt if the item does not exist. - */ + /** + * Return the digest associated with the key. + * + * @return std::nullopt if the item does not exist. + */ [[nodiscard]] virtual std::optional digest(key_type const& key) const = 0; }; diff --git a/include/xrpl/ledger/Sandbox.h b/include/xrpl/ledger/Sandbox.h index ca8838631f..fd48e339eb 100644 --- a/include/xrpl/ledger/Sandbox.h +++ b/include/xrpl/ledger/Sandbox.h @@ -7,12 +7,13 @@ namespace xrpl { -/** Discardable, editable view to a ledger. - - The sandbox inherits the flags of the base. - - @note Presented as ApplyView to clients. -*/ +/** + * Discardable, editable view to a ledger. + * + * The sandbox inherits the flags of the base. + * + * @note Presented as ApplyView to clients. + */ class Sandbox : public detail::ApplyViewBase { public: diff --git a/include/xrpl/ledger/View.h b/include/xrpl/ledger/View.h index c89764df1d..768e518008 100644 --- a/include/xrpl/ledger/View.h +++ b/include/xrpl/ledger/View.h @@ -35,26 +35,27 @@ enum class SkipEntry : bool { No = false, Yes }; // //------------------------------------------------------------------------------ -/** Determines whether the given expiration time has passed. - - In the XRP Ledger, expiration times are defined as the number of whole - seconds after the "XRPL epoch" which, for historical reasons, is set - to January 1, 2000 (00:00 UTC). - - This is like the way the Unix epoch works, except the XRPL epoch is - precisely 946,684,800 seconds after the Unix Epoch. - - See https://xrpl.org/basic-data-types.html#specifying-time - - Expiration is defined in terms of the close time of the parent ledger, - because we definitively know the time that it closed (since consensus - agrees on time) but we do not know the closing time of the ledger that - is under construction. - - @param view The ledger whose parent time is used as the clock. - @param exp The optional expiration time we want to check. - - @returns `true` if `exp` is in the past; `false` otherwise. +/** + * Determines whether the given expiration time has passed. + * + * In the XRP Ledger, expiration times are defined as the number of whole + * seconds after the "XRPL epoch" which, for historical reasons, is set + * to January 1, 2000 (00:00 UTC). + * + * This is like the way the Unix epoch works, except the XRPL epoch is + * precisely 946,684,800 seconds after the Unix Epoch. + * + * See https://xrpl.org/basic-data-types.html#specifying-time + * + * Expiration is defined in terms of the close time of the parent ledger, + * because we definitively know the time that it closed (since consensus + * agrees on time) but we do not know the closing time of the ledger that + * is under construction. + * + * @param view The ledger whose parent time is used as the clock. + * @param exp The optional expiration time we want to check. + * + * @return `true` if `exp` is in the past; `false` otherwise. */ [[nodiscard]] bool hasExpired(ReadView const& view, std::optional const& exp); @@ -83,41 +84,44 @@ using majorityAmendments_t = std::map; [[nodiscard]] majorityAmendments_t getMajorityAmendments(ReadView const& view); -/** Return the hash of a ledger by sequence. - The hash is retrieved by looking up the "skip list" - in the passed ledger. As the skip list is limited - in size, if the requested ledger sequence number is - out of the range of ledgers represented in the skip - list, then std::nullopt is returned. - @return The hash of the ledger with the - given sequence number or std::nullopt. -*/ +/** + * Return the hash of a ledger by sequence. + * The hash is retrieved by looking up the "skip list" + * in the passed ledger. As the skip list is limited + * in size, if the requested ledger sequence number is + * out of the range of ledgers represented in the skip + * list, then std::nullopt is returned. + * @return The hash of the ledger with the + * given sequence number or std::nullopt. + */ [[nodiscard]] std::optional hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal); -/** Find a ledger index from which we could easily get the requested ledger - - The index that we return should meet two requirements: - 1) It must be the index of a ledger that has the hash of the ledger - we are looking for. This means that its sequence must be equal to - greater than the sequence that we want but not more than 256 greater - since each ledger contains the hashes of the 256 previous ledgers. - - 2) Its hash must be easy for us to find. This means it must be 0 mod 256 - because every such ledger is permanently enshrined in a LedgerHashes - page which we can easily retrieve via the skip list. -*/ +/** + * Find a ledger index from which we could easily get the requested ledger + * + * The index that we return should meet two requirements: + * 1) It must be the index of a ledger that has the hash of the ledger + * we are looking for. This means that its sequence must be equal to + * greater than the sequence that we want but not more than 256 greater + * since each ledger contains the hashes of the 256 previous ledgers. + * + * 2) Its hash must be easy for us to find. This means it must be 0 mod 256 + * because every such ledger is permanently enshrined in a LedgerHashes + * page which we can easily retrieve via the skip list. + */ inline LedgerIndex getCandidateLedger(LedgerIndex requested) { return (requested + 255) & (~255); } -/** Return false if the test ledger is provably incompatible - with the valid ledger, that is, they could not possibly - both be valid. Use the first form if you have both ledgers, - use the second form if you have not acquired the valid ledger yet -*/ +/** + * Return false if the test ledger is provably incompatible + * with the valid ledger, that is, they could not possibly + * both be valid. Use the first form if you have both ledgers, + * use the second form if you have not acquired the valid ledger yet + */ [[nodiscard]] bool areCompatible( ReadView const& validLedger, @@ -146,7 +150,8 @@ dirLink( SLE::pointer& object, SF_UINT64 const& node = sfOwnerNode); -/** Checks that can withdraw funds from an object to itself or a destination. +/** + * Checks that can withdraw funds from an object to itself or a destination. * * The receiver may be either the submitting account (sfAccount) or a different * destination account (sfDestination). @@ -169,7 +174,8 @@ canWithdraw( STAmount const& amount, bool hasDestinationTag); -/** Checks that can withdraw funds from an object to itself or a destination. +/** + * Checks that can withdraw funds from an object to itself or a destination. * * The receiver may be either the submitting account (sfAccount) or a different * destination account (sfDestination). @@ -191,7 +197,8 @@ canWithdraw( STAmount const& amount, bool hasDestinationTag); -/** Checks that can withdraw funds from an object to itself or a destination. +/** + * Checks that can withdraw funds from an object to itself or a destination. * * The receiver may be either the submitting account (sfAccount) or a different * destination account (sfDestination). @@ -210,8 +217,7 @@ canWithdraw(ReadView const& view, STTx const& tx); [[nodiscard]] TER doWithdraw( - ApplyView& view, - STTx const& tx, + ApplyViewContext ctx, AccountID const& senderAcct, AccountID const& dstAcct, AccountID const& sourceAcct, @@ -219,13 +225,15 @@ doWithdraw( STAmount const& amount, beast::Journal j); -/** Deleter function prototype. Returns the status of the entry deletion +/** + * Deleter function prototype. Returns the status of the entry deletion * (if should not be skipped) and if the entry should be skipped. The status * is always tesSUCCESS if the entry should be skipped. */ using EntryDeleter = std::function(LedgerEntryType, uint256 const&, SLE::pointer&)>; -/** Cleanup owner directory entries on account delete. +/** + * Cleanup owner directory entries on account delete. * Used for a regular and AMM accounts deletion. The caller * has to provide the deleter function, which handles details of * specific account-owned object deletion. @@ -240,12 +248,13 @@ cleanupOnAccountDelete( beast::Journal j, std::optional maxNodesToDelete = std::nullopt); -/** Has the specified time passed? - - @param now the current time - @param mark the cutoff point - @return true if \a now refers to a time strictly after \a mark, else false. -*/ +/** + * Has the specified time passed? + * + * @param now the current time + * @param mark the cutoff point + * @return true if \a now refers to a time strictly after \a mark, else false. + */ bool after(NetClock::time_point now, std::uint32_t mark); diff --git a/include/xrpl/ledger/helpers/AMMHelpers.h b/include/xrpl/ledger/helpers/AMMHelpers.h index c6a2053010..7d41bfce81 100644 --- a/include/xrpl/ledger/helpers/AMMHelpers.h +++ b/include/xrpl/ledger/helpers/AMMHelpers.h @@ -53,7 +53,8 @@ enum class IsDeposit : bool { No = false, Yes = true }; inline Number const kAMMInvariantRelativeTolerance{1, -11}; -/** Calculate LP Tokens given AMM pool reserves. +/** + * Calculate LP Tokens given AMM pool reserves. * @param asset1 AMM one side of the pool reserve * @param asset2 AMM another side of the pool reserve * @return LP Tokens as IOU @@ -61,7 +62,8 @@ inline Number const kAMMInvariantRelativeTolerance{1, -11}; STAmount ammLPTokens(STAmount const& asset1, STAmount const& asset2, Asset const& lptIssue); -/** Calculate LP Tokens given asset's deposit amount. +/** + * Calculate LP Tokens given asset's deposit amount. * @param asset1Balance current AMM asset1 balance * @param asset1Deposit requested asset1 deposit amount * @param lptAMMBalance AMM LPT balance @@ -75,10 +77,11 @@ lpTokensOut( STAmount const& lptAMMBalance, std::uint16_t tfee); -/** Calculate asset deposit given LP Tokens. +/** + * Calculate asset deposit given LP Tokens. * @param asset1Balance current AMM asset1 balance - * @param lpTokens LP Tokens * @param lptAMMBalance AMM LPT balance + * @param lpTokens LP Tokens * @param tfee trading fee in basis points * @return */ @@ -89,7 +92,8 @@ ammAssetIn( STAmount const& lpTokens, std::uint16_t tfee); -/** Calculate LP Tokens given asset's withdraw amount. Return 0 +/** + * Calculate LP Tokens given asset's withdraw amount. Return 0 * if can't calculate. * @param asset1Balance current AMM asset1 balance * @param asset1Withdraw requested asset1 withdraw amount @@ -104,7 +108,8 @@ lpTokensIn( STAmount const& lptAMMBalance, std::uint16_t tfee); -/** Calculate asset withdrawal by tokens +/** + * Calculate asset withdrawal by tokens * @param assetBalance balance of the asset being withdrawn * @param lptAMMBalance total AMM Tokens balance * @param lpTokens LP Tokens balance @@ -118,7 +123,8 @@ ammAssetOut( STAmount const& lpTokens, std::uint16_t tfee); -/** Check if the relative distance between the qualities +/** + * Check if the relative distance between the qualities * is within the requested distance. * @param calcQuality calculated quality * @param reqQuality requested quality @@ -137,7 +143,8 @@ withinRelativeDistance(Quality const& calcQuality, Quality const& reqQuality, Nu return ((min.rate() - max.rate()) / min.rate()) < dist; } -/** Check if the relative distance between the amounts +/** + * Check if the relative distance between the amounts * is within the requested distance. * @param calc calculated amount * @param req requested amount @@ -158,13 +165,15 @@ withinRelativeDistance(Amt const& calc, Amt const& req, Number const& dist) return ((max - min) / max) < dist; } -/** Solve quadratic equation to find takerGets or takerPays. Round +/** + * Solve quadratic equation to find takerGets or takerPays. Round * to minimize the amount in order to maximize the quality. */ std::optional solveQuadraticEqSmallest(Number const& a, Number const& b, Number const& c); -/** Generate AMM offer starting with takerGets when AMM pool +/** + * Generate AMM offer starting with takerGets when AMM pool * from the payment perspective is IOU(in)/XRP(out) * Equations: * Spot Price Quality after the offer is consumed: @@ -231,7 +240,8 @@ getAMMOfferStartWithTakerGets( return amounts; } -/** Generate AMM offer starting with takerPays when AMM pool +/** + * Generate AMM offer starting with takerPays when AMM pool * from the payment perspective is XRP(in)/IOU(out) or IOU(in)/IOU(out). * Equations: * Spot Price Quality after the offer is consumed: @@ -298,7 +308,8 @@ getAMMOfferStartWithTakerPays( return amounts; } -/** Generate AMM offer so that either updated Spot Price Quality (SPQ) +/** + * Generate AMM offer so that either updated Spot Price Quality (SPQ) * is equal to LOB quality (in this case AMM offer quality is * better than LOB quality) or AMM offer is equal to LOB quality * (in this case SPQ is better than LOB quality). @@ -415,7 +426,8 @@ changeSpotPriceQuality( return amounts; } -/** AMM pool invariant - the product (A * B) after swap in/out has to remain +/** + * AMM pool invariant - the product (A * B) after swap in/out has to remain * at least the same: (A + in) * (B - out) >= A * B * XRP round-off may result in a smaller product after swap in/out. * To address this: @@ -427,7 +439,8 @@ changeSpotPriceQuality( * value is increased. */ -/** Swap assetIn into the pool and swap out a proportional amount +/** + * Swap assetIn into the pool and swap out a proportional amount * of the other asset. Implements AMM Swap in. * @see [XLS30d:AMM * Swap](https://github.com/XRPLF/XRPL-Standards/discussions/78) @@ -493,7 +506,8 @@ swapAssetIn(TAmounts const& pool, TIn const& assetIn, std::uint16_t t Number::RoundingMode::Downward); } -/** Swap assetOut out of the pool and swap in a proportional amount +/** + * Swap assetOut out of the pool and swap in a proportional amount * of the other asset. Implements AMM Swap out. * @see [XLS30d:AMM * Swap](https://github.com/XRPLF/XRPL-Standards/discussions/78) @@ -559,12 +573,14 @@ swapAssetOut(TAmounts const& pool, TOut const& assetOut, std::uint16_ Number::RoundingMode::Upward); } -/** Return square of n. +/** + * Return square of n. */ Number square(Number const& n); -/** Adjust LP tokens to deposit/withdraw. +/** + * Adjust LP tokens to deposit/withdraw. * Amount type keeps 16 digits. Maintaining the LP balance by adding * deposited tokens or subtracting withdrawn LP tokens from LP balance * results in losing precision in LP balance. I.e. the resulting LP balance @@ -578,7 +594,8 @@ square(Number const& n); STAmount adjustLPTokens(STAmount const& lptAMMBalance, STAmount const& lpTokens, IsDeposit isDeposit); -/** Calls adjustLPTokens() and adjusts deposit or withdraw amounts if +/** + * Calls adjustLPTokens() and adjusts deposit or withdraw amounts if * the adjusted LP tokens are less than the provided LP tokens. * @param amountBalance asset1 pool balance * @param amount asset1 to deposit or withdraw @@ -599,7 +616,8 @@ adjustAmountsByLPTokens( std::uint16_t tfee, IsDeposit isDeposit); -/** Positive solution for quadratic equation: +/** + * Positive solution for quadratic equation: * x = (-b + sqrt(b**2 + 4*a*c))/(2*a) */ Number @@ -630,7 +648,8 @@ getAssetRounding(IsDeposit isDeposit) } // namespace detail -/** Round AMM equal deposit/withdrawal amount. Deposit/withdrawal formulas +/** + * Round AMM equal deposit/withdrawal amount. Deposit/withdrawal formulas * calculate the amount as a fractional value of the pool balance. The rounding * takes place on the last step of multiplying the balance by the fraction if * AMMv1_3 is enabled. @@ -654,7 +673,8 @@ getRoundedAsset(Rules const& rules, STAmount const& balance, A const& frac, IsDe return multiply(balance, frac, rm); } -/** Round AMM single deposit/withdrawal amount. +/** + * Round AMM single deposit/withdrawal amount. * The lambda's are used to delay evaluation until the function * is executed so that the calculation is not done twice. noRoundCb() is * called if AMMv1_3 is disabled. Otherwise, the rounding is set and @@ -671,7 +691,8 @@ getRoundedAsset( std::function const& productCb, IsDeposit isDeposit); -/** Round AMM deposit/withdrawal LPToken amount. Deposit/withdrawal formulas +/** + * Round AMM deposit/withdrawal LPToken amount. Deposit/withdrawal formulas * calculate the lptokens as a fractional value of the AMM total lptokens. * The rounding takes place on the last step of multiplying the balance by * the fraction if AMMv1_3 is enabled. The tokens are then @@ -685,7 +706,8 @@ getRoundedLPTokens( Number const& frac, IsDeposit isDeposit); -/** Round AMM single deposit/withdrawal LPToken amount. +/** + * Round AMM single deposit/withdrawal LPToken amount. * The lambda's are used to delay evaluation until the function is executed * so that the calculations are not done twice. * noRoundCb() is called if AMMv1_3 is disabled. Otherwise, the rounding is set @@ -732,7 +754,8 @@ adjustAssetOutByTokens( STAmount const& tokens, std::uint16_t tfee); -/** Find a fraction of tokens after the tokens are adjusted. The fraction +/** + * Find a fraction of tokens after the tokens are adjusted. The fraction * is used to adjust equal deposit/withdraw amount. */ Number @@ -742,7 +765,8 @@ adjustFracByTokens( STAmount const& tokens, Number const& frac); -/** Get AMM pool balances. +/** + * Get AMM pool balances. */ std::pair ammPoolHolds( @@ -754,7 +778,8 @@ ammPoolHolds( AuthHandling authHandling, beast::Journal const j); -/** Check AMM pool product invariant after an AMM operation that changes LP tokens +/** + * Check AMM pool product invariant after an AMM operation that changes LP tokens * (deposit/withdraw/clawback) from an already calculated pool product mean. * Returns tecPRECISION_LOSS if poolProductMean < newLPTokenBalance beyond the * invariant tolerance, @@ -763,7 +788,8 @@ ammPoolHolds( TER checkAMMPrecisionLoss(Number const& poolProductMean, STAmount const& newLPTokenBalance); -/** Check AMM pool product invariant after an AMM operation that changes LP tokens +/** + * Check AMM pool product invariant after an AMM operation that changes LP tokens * (deposit/withdraw/clawback). * Returns tecPRECISION_LOSS if sqrt(asset1 * asset2) < newLPTokenBalance beyond * the invariant tolerance, @@ -778,7 +804,8 @@ checkAMMPrecisionLoss( STAmount const& newLPTokenBalance, beast::Journal const j); -/** Get AMM pool and LP token balances. If both optIssue are +/** + * Get AMM pool and LP token balances. If both optIssue are * provided then they are used as the AMM token pair issues. * Otherwise the missing issues are fetched from ammSle. */ @@ -792,7 +819,8 @@ ammHolds( AuthHandling authHandling, beast::Journal const j); -/** Get the balance of LP tokens. +/** + * Get the balance of LP tokens. */ STAmount ammLPHolds( @@ -810,25 +838,29 @@ ammLPHolds( AccountID const& lpAccount, beast::Journal const j); -/** Get AMM trading fee for the given account. The fee is discounted +/** + * Get AMM trading fee for the given account. The fee is discounted * if the account is the auction slot owner or one of the slot's authorized * accounts. */ std::uint16_t getTradingFee(ReadView const& view, SLE const& ammSle, AccountID const& account); -/** Returns total amount held by AMM for the given token. +/** + * Returns total amount held by AMM for the given token. */ STAmount ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Asset const& asset); -/** Delete trustlines to AMM. If all trustlines are deleted then +/** + * Delete trustlines to AMM. If all trustlines are deleted then * AMM object and account are deleted. Otherwise tecINCOMPLETE is returned. */ TER deleteAMMAccount(Sandbox& view, Asset const& asset, Asset const& asset2, beast::Journal j); -/** Initialize Auction and Voting slots and set the trading/discounted fee. +/** + * Initialize Auction and Voting slots and set the trading/discounted fee. */ void initializeFeeAuctionVote( @@ -838,14 +870,16 @@ initializeFeeAuctionVote( Asset const& lptAsset, std::uint16_t tfee); -/** Return true if the Liquidity Provider is the only AMM provider, false +/** + * Return true if the Liquidity Provider is the only AMM provider, false * otherwise. Return tecINTERNAL if encountered an unexpected condition, * for instance Liquidity Provider has more than one LPToken trustline. */ std::expected isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID const& lpAccount); -/** Due to rounding, the LPTokenBalance of the last LP might +/** + * Due to rounding, the LPTokenBalance of the last LP might * not match the LP's trustline balance. If it's within the tolerance, * update LPTokenBalance to match the LP's trustline balance. */ diff --git a/include/xrpl/ledger/helpers/AccountRootHelpers.h b/include/xrpl/ledger/helpers/AccountRootHelpers.h index d0cfc175a3..350fc6ca85 100644 --- a/include/xrpl/ledger/helpers/AccountRootHelpers.h +++ b/include/xrpl/ledger/helpers/AccountRootHelpers.h @@ -14,35 +14,324 @@ #include #include +#include #include #include namespace xrpl { -/** Check if the issuer has the global freeze flag set. - @param issuer The account to check - @return true if the account has global freeze set -*/ +/** + * Check if the issuer has the global freeze flag set. + * @param issuer The account to check + * @return true if the account has global freeze set + */ [[nodiscard]] bool isGlobalFrozen(ReadView const& view, AccountID const& issuer); -// Calculate liquid XRP balance for an account. -// This function may be used to calculate the amount of XRP that -// the holder is able to freely spend. It subtracts reserve requirements. -// -// ownerCountAdj adjusts the owner count in case the caller calculates -// before ledger entries are added or removed. Positive to add, negative -// to subtract. -// -// @param ownerCountAdj positive to add to count, negative to reduce count. +/** + * Calculate liquid XRP balance for an account. + * + * This function may be used to calculate the amount of XRP that + * the holder is able to freely spend. It subtracts reserve requirements. + * + * ownerCountAdj adjusts the owner count in case the caller calculates + * before ledger entries are added or removed. Positive to add, negative + * to subtract. + * + * @param view The ledger view to read from + * @param id The account ID to check + * @param ownerCountAdj Positive to add to count, negative to reduce count + * @param j Journal for logging + * @return The liquid XRP amount available to the account + */ [[nodiscard]] XRPAmount xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, beast::Journal j); -/** Adjust the owner count up or down. */ -void -adjustOwnerCount(ApplyView& view, SLE::ref sle, std::int32_t amount, beast::Journal j); +struct Adjustment +{ + std::int32_t ownerCountDelta = 0; + std::int32_t accountCountDelta = 0; +}; -/** Returns IOU issuer transfer fee as Rate. Rate specifies +/** + * Returns the account reserve, in drops. + * + * Actual owner count can be adjusted by delta in ownerCountAdj + * Actual reserve count can be adjusted by delta in accountCountAdj + * The reserve is calculated as: + * (ownerCount + "sponsoring object count" - "sponsored object count" + additionalOwnerCount) * + * increment + (1 if not sponsored account + sponsoringAccountCount) * "reserve base" + * + * @param view The ledger view to read from + * @param sle The ledger entry for the account + * @param j Journal for logging + * @param adj Adjustment to the owner/account count (default: 0/0). Positive to add, negative to + * subtract. + * @return The account reserve amount in drops + */ +[[nodiscard]] XRPAmount +accountReserve(ReadView const& view, SLE::const_ref sle, beast::Journal j, Adjustment adj = {}); + +/** + * Convenience overload that accepts AccountID instead of SLE. + * + * @param view The ledger view to read from + * @param id The account ID + * @param j Journal for logging + * @param adj Adjustment to the owner/account count (default: 0/0). Positive to add, negative to + * subtract. + * @return The account reserve amount in drops + */ +[[nodiscard]] inline XRPAmount +accountReserve(ReadView const& view, AccountID const& id, beast::Journal j, Adjustment adj = {}) +{ + return accountReserve(view, view.read(keylet::account(id)), j, adj); +} + +/** + * Check if an account has sufficient reserve. + * + * @param view The ledger view to read from + * @param tx The transaction being processed + * @param accSle The account's ledger entry + * @param accBalance The account's balance + * @param sponsorSle The sponsor's ledger entry (if applicable) + * @param adj Adjustment to the owner/account count (default: 0/0). Positive to add, negative to + * subtract. + * @param j Journal for logging (default: null sink) + * @param insufReserveCode The transaction result code to return if the reserve is insufficient + * (default: tecINSUFFICIENT_RESERVE). + * @return Transaction result code + */ +[[nodiscard]] TER +checkReserve( + ApplyViewContext ctx, + SLE::const_ref accSle, + XRPAmount accBalance, + SLE::const_ref sponsorSle, + Adjustment adj, + beast::Journal j, + TER insufReserveCode = tecINSUFFICIENT_RESERVE); + +/** + * Check if an account has sufficient reserve, deriving the sponsor internally. + * + * Equivalent to the overload above, but resolves the sponsor via + * getEffectiveTxReserveSponsor(ctx, accSle) instead of taking it explicitly. Use this + * in the common case where the sponsor is simply the transaction's reserve + * sponsor for accSle. Callers that must force the account's-own-reserve branch + * (passing a null sponsor) or supply a different sponsor should use the + * explicit overload above. + * + * @param ctx The apply-view context (view + tx) + * @param accSle The account's ledger entry + * @param accBalance The account's balance + * @param adj Reserve adjustments (owner/account count deltas) + * @param j Journal for logging (default: null sink) + * @return Transaction result code + */ +[[nodiscard]] TER +checkReserve( + ApplyViewContext ctx, + SLE::const_ref accSle, + XRPAmount accBalance, + Adjustment adj, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}); + +/** + * Return number of the objects which reserve is covered by the account(sle) (so called "owner + * count"). Actual owner count can be adjusted by delta in ownerCountAdj. + * + * @param sle The account's ledger entry + * @param j Journal for logging + * @param ownerCountAdj Adjustment to the owner count (default: 0) + * @return The adjusted owner count + */ +std::uint32_t +ownerCount(SLE::const_ref sle, beast::Journal j, std::int32_t ownerCountAdj = 0); + +/** + * Increase owner-count fields when the caller supplies the sponsor. + * + * This helper does not create a ledger object. It updates reserve accounting + * after the caller has created/updated an object. + * If sponsorSle is provided, this also adjusts the account's sponsored count + * and the sponsor's sponsoring count. + * + * @param view The apply view for making changes + * @param accountSle The account's ledger entry + * @param sponsorSle The sponsor's ledger entry (if applicable) + * @param count Amount to add to the owner count + * @param j Journal for logging + */ +void +increaseOwnerCount( + ApplyView& view, + SLE::ref accountSle, + SLE::ref sponsorSle, + std::uint32_t count, + beast::Journal j); + +/** + * Increase owner-count fields, deriving the tx reserve sponsor internally. + * + * Equivalent to the overload above, but resolves the sponsor via + * getEffectiveTxReserveSponsor(ctx, accountSle) instead of taking it explicitly. Use + * this when the sponsor is the transaction's reserve sponsor for accountSle + * (the common create path). Deletion paths, which derive the sponsor from an + * object's sfSponsor field, should keep using the explicit overload. + * + * @param ctx The apply-view context (view + tx) + * @param accountSle The account's ledger entry + * @param count Amount to add to the owner count + * @param j Journal for logging + */ +void +increaseOwnerCount( + ApplyViewContext ctx, + SLE::ref accountSle, + std::uint32_t count, + beast::Journal j); + +/** + * Convenience overload that accepts AccountID instead of SLE references. + * + * @param view The apply view for making changes + * @param account The account ID + * @param sponsor The optional sponsor account ID + * @param count Amount to add to the owner count + * @param j Journal for logging + */ +inline void +increaseOwnerCount( + ApplyView& view, + AccountID const& account, + std::optional const& sponsor, + std::uint32_t count, + beast::Journal j) +{ + increaseOwnerCount( + view, + view.peek(keylet::account(account)), + sponsor ? view.peek(keylet::account(*sponsor)) : SLE::pointer(), + count, + j); +} + +/** + * Decrease owner-count fields when the caller supplies the sponsor. + * + * This helper does not delete a ledger object. It updates reserve accounting + * after the caller has removed an owner-counted reserve, or for special + * owner-count changes whose sponsor cannot be derived from an object's + * sfSponsor field. + * + * @param view The apply view for making changes + * @param accountSle The account's ledger entry + * @param sponsorSle The sponsor's ledger entry (if applicable) + * @param count Amount to remove from the owner count + * @param j Journal for logging + */ +void +decreaseOwnerCount( + ApplyView& view, + SLE::ref accountSle, + SLE::ref sponsorSle, + std::uint32_t count, + beast::Journal j); + +/** + * Convenience overload that accepts AccountID instead of SLE references. + * + * @param view The apply view for making changes + * @param account The account ID + * @param sponsor The optional sponsor account ID + * @param count Amount to remove from the owner count + * @param j Journal for logging + */ +inline void +decreaseOwnerCount( + ApplyView& view, + AccountID const& account, + std::optional const& sponsor, + std::uint32_t count, + beast::Journal j) +{ + decreaseOwnerCount( + view, + view.peek(keylet::account(account)), + sponsor ? view.peek(keylet::account(*sponsor)) : SLE::pointer(), + count, + j); +} + +/** + * Decrease owner-count fields for an existing ledger object. + * + * This helper derives the reserve sponsor from objectSle's sfSponsor field, + * then updates the same owner-count fields as decreaseOwnerCount. Use this + * when removing an existing object whose reserve sponsor is stored on that + * object. + * + * @param view The apply view for making changes + * @param accountSle The account's ledger entry + * @param objectSle The object's ledger entry + * @param count Amount to remove from the owner count + * @param j Journal for logging + */ +void +decreaseOwnerCountForObject( + ApplyView& view, + SLE::ref accountSle, + SLE::ref objectSle, + std::uint32_t count, + beast::Journal j); + +/** + * Convenience overload that accepts AccountID instead of account SLE reference. + * + * @param view The apply view for making changes + * @param account The account ID + * @param objectSle The object's ledger entry + * @param count Amount to remove from the owner count + * @param j Journal for logging + */ +inline void +decreaseOwnerCountForObject( + ApplyView& view, + AccountID const& account, + SLE::ref objectSle, + std::uint32_t count, + beast::Journal j) +{ + SLE::ref accountSle = view.peek(keylet::account(account)); + decreaseOwnerCountForObject(view, accountSle, objectSle, count, j); +} + +/** + * Adjust a LoanBroker's owner count. + * + * A LoanBroker's sfOwnerCount tracks the number of outstanding loans on + * that broker; it is not a reserve-backed owner count and is distinct + * from the broker's pseudo-account's owner count. Loans can never carry a + * reserve sponsor (LoanSet rejects reserve sponsorship at preflight), so + * this never involves sponsor accounting and never invokes the + * ownerCountHook used for ACCOUNT_ROOT reserve tracking. + * + * @param view The apply view for making changes + * @param brokerSle The LoanBroker's ledger entry + * @param delta Amount to add (positive) or remove (negative) from the count + * @param j Journal for logging + */ +void +adjustLoanBrokerOwnerCount( + ApplyView& view, + SLE::ref brokerSle, + std::int32_t delta, + beast::Journal j); + +/** + * Returns IOU issuer transfer fee as Rate. Rate specifies * the fee as fractions of 1 billion. For example, 1% transfer rate * is represented as 1,010,000,000. * @param issuer The IOU issuer @@ -50,35 +339,40 @@ adjustOwnerCount(ApplyView& view, SLE::ref sle, std::int32_t amount, beast::Jour [[nodiscard]] Rate transferRate(ReadView const& view, AccountID const& issuer); -/** Generate a pseudo-account address from a pseudo owner key. - @param pseudoOwnerKey The key to generate the address from - @return The generated account ID -*/ +/** + * Generate a pseudo-account address from a pseudo owner key. + * @param pseudoOwnerKey The key to generate the address from + * @return The generated account ID + */ AccountID pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey); -/** Returns the list of fields that define an ACCOUNT_ROOT as a pseudo-account - if set. - - The list is constructed during initialization and is const after that. - Pseudo-account designator fields MUST be maintained by including the - SField::sMD_PseudoAccount flag in the SField definition. -*/ +/** + * Returns the list of fields that define an ACCOUNT_ROOT as a pseudo-account + * if set. + * + * The list is constructed during initialization and is const after that. + * Pseudo-account designator fields MUST be maintained by including the + * SField::sMD_PseudoAccount flag in the SField definition. + */ [[nodiscard]] std::vector const& getPseudoAccountFields(); -/** Returns true if and only if sleAcct is a pseudo-account or specific - pseudo-accounts in pseudoFieldFilter. - - Returns false if sleAcct is: - - NOT a pseudo-account OR - - NOT a ltACCOUNT_ROOT OR - - null pointer -*/ +/** + * Returns true if and only if sleAcct is a pseudo-account or specific + * pseudo-accounts in pseudoFieldFilter. + * + * Returns false if sleAcct is: + * - NOT a pseudo-account OR + * - NOT a ltACCOUNT_ROOT OR + * - null pointer + */ [[nodiscard]] bool isPseudoAccount(SLE::const_pointer sleAcct, std::set const& pseudoFieldFilter = {}); -/** Convenience overload that reads the account from the view. */ +/** + * Convenience overload that reads the account from the view. + */ [[nodiscard]] inline bool isPseudoAccount( ReadView const& view, @@ -99,11 +393,12 @@ isPseudoAccount( [[nodiscard]] std::expected createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const& ownerField); -/** Checks the destination and tag. - - - Checks that the SLE is not null. - - If the SLE requires a destination tag, checks that there is a tag. -*/ +/** + * Checks the destination and tag. + * + * - Checks that the SLE is not null. + * - If the SLE requires a destination tag, checks that there is a tag. + */ [[nodiscard]] TER checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag); diff --git a/include/xrpl/ledger/helpers/DirectoryHelpers.h b/include/xrpl/ledger/helpers/DirectoryHelpers.h index a95b9bc95a..25085c4252 100644 --- a/include/xrpl/ledger/helpers/DirectoryHelpers.h +++ b/include/xrpl/ledger/helpers/DirectoryHelpers.h @@ -95,19 +95,20 @@ internalDirFirst( } // namespace detail /** @{ */ -/** Returns the first entry in the directory, advancing the index - - @deprecated These are legacy function that are considered deprecated - and will soon be replaced with an iterator-based model - that is easier to use. You should not use them in new code. - - @param view The view against which to operate - @param root The root (i.e. first page) of the directory to iterate - @param page The current page - @param index The index inside the current page - @param entry The entry at the current index - - @return true if the directory isn't empty; false otherwise +/** + * Returns the first entry in the directory, advancing the index + * + * @deprecated These are legacy function that are considered deprecated + * and will soon be replaced with an iterator-based model + * that is easier to use. You should not use them in new code. + * + * @param view The view against which to operate + * @param root The root (i.e. first page) of the directory to iterate + * @param page The current page + * @param index The index inside the current page + * @param entry The entry at the current index + * + * @return true if the directory isn't empty; false otherwise */ bool cdirFirst( @@ -127,19 +128,20 @@ dirFirst( /** @} */ /** @{ */ -/** Returns the next entry in the directory, advancing the index - - @deprecated These are legacy function that are considered deprecated - and will soon be replaced with an iterator-based model - that is easier to use. You should not use them in new code. - - @param view The view against which to operate - @param root The root (i.e. first page) of the directory to iterate - @param page The current page - @param index The index inside the current page - @param entry The entry at the current index - - @return true if the directory isn't empty; false otherwise +/** + * Returns the next entry in the directory, advancing the index + * + * @deprecated These are legacy function that are considered deprecated + * and will soon be replaced with an iterator-based model + * that is easier to use. You should not use them in new code. + * + * @param view The view against which to operate + * @param root The root (i.e. first page) of the directory to iterate + * @param page The current page + * @param index The index inside the current page + * @param entry The entry at the current index + * + * @return true if the directory isn't empty; false otherwise */ bool cdirNext( @@ -158,16 +160,19 @@ dirNext( uint256& entry); /** @} */ -/** Iterate all items in the given directory. */ +/** + * Iterate all items in the given directory. + */ void forEachItem(ReadView const& view, Keylet const& root, std::function const& f); -/** Iterate all items after an item in the given directory. - @param after The key of the item to start after - @param hint The directory page containing `after` - @param limit The maximum number of items to return - @return `false` if the iteration failed -*/ +/** + * Iterate all items after an item in the given directory. + * @param after The key of the item to start after + * @param hint The directory page containing `after` + * @param limit The maximum number of items to return + * @return `false` if the iteration failed + */ bool forEachItemAfter( ReadView const& view, @@ -177,19 +182,22 @@ forEachItemAfter( unsigned int limit, std::function const& f); -/** Iterate all items in an account's owner directory. */ +/** + * Iterate all items in an account's owner directory. + */ inline void forEachItem(ReadView const& view, AccountID const& id, std::function const& f) { forEachItem(view, keylet::ownerDir(id), f); } -/** Iterate all items after an item in an owner directory. - @param after The key of the item to start after - @param hint The directory page containing `after` - @param limit The maximum number of items to return - @return `false` if the iteration failed -*/ +/** + * Iterate all items after an item in an owner directory. + * @param after The key of the item to start after + * @param hint The directory page containing `after` + * @param limit The maximum number of items to return + * @return `false` if the iteration failed + */ inline bool forEachItemAfter( ReadView const& view, @@ -202,13 +210,16 @@ forEachItemAfter( return forEachItemAfter(view, keylet::ownerDir(id), after, hint, limit, f); } -/** Returns `true` if the directory is empty - @param key The key of the directory -*/ +/** + * Returns `true` if the directory is empty + * @param key The key of the directory + */ [[nodiscard]] bool dirIsEmpty(ReadView const& view, Keylet const& k); -/** Returns a function that sets the owner on a directory SLE */ +/** + * Returns a function that sets the owner on a directory SLE + */ [[nodiscard]] std::function describeOwnerDir(AccountID const& account); diff --git a/include/xrpl/ledger/helpers/EscrowHelpers.h b/include/xrpl/ledger/helpers/EscrowHelpers.h index d173c33c56..9f54e53769 100644 --- a/include/xrpl/ledger/helpers/EscrowHelpers.h +++ b/include/xrpl/ledger/helpers/EscrowHelpers.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -22,17 +23,15 @@ #include #include -#include - namespace xrpl { template TER escrowUnlockApplyHelper( - ApplyView& view, + ApplyViewContext ctx, Rate lockedRate, SLE::ref sleDest, - STAmount const& xrpBalance, + XRPAmount xrpBalance, STAmount const& amount, AccountID const& issuer, AccountID const& sender, @@ -43,10 +42,10 @@ escrowUnlockApplyHelper( template <> inline TER escrowUnlockApplyHelper( - ApplyView& view, + ApplyViewContext ctx, Rate lockedRate, SLE::ref sleDest, - STAmount const& xrpBalance, + XRPAmount xrpBalance, STAmount const& amount, AccountID const& issuer, AccountID const& sender, @@ -66,16 +65,26 @@ escrowUnlockApplyHelper( if (receiverIssuer) return tesSUCCESS; - if (!view.exists(trustLineKey) && createAsset) + if (!ctx.view.exists(trustLineKey) && createAsset) { // Can the account cover the trust line's reserve? - if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)}; - xrpBalance < view.fees().accountReserve(ownerCount + 1)) + auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, sleDest); + if (!sponsorSle) + return sponsorSle.error(); // LCOV_EXCL_LINE + + if (auto const ret = checkReserve( + ctx, + sleDest, + xrpBalance, + *sponsorSle, + {.ownerCountDelta = 1}, + journal, + tecNO_LINE_INSUF_RESERVE); + !isTesSuccess(ret)) { JLOG(journal.trace()) << "Trust line does not exist. " "Insufficient reserve to create line."; - - return tecNO_LINE_INSUF_RESERVE; + return ret; } Currency const currency = issue.currency; @@ -83,7 +92,7 @@ escrowUnlockApplyHelper( initialBalance.get().account = noAccount(); if (TER const ter = trustCreate( - view, // payment sandbox + ctx.view, // payment sandbox recvLow, // is dest low? issuer, // source receiver, // destination @@ -97,19 +106,20 @@ escrowUnlockApplyHelper( Issue(currency, receiver), // limit of zero 0, // quality in 0, // quality out + *sponsorSle, // sponsor journal); // journal !isTesSuccess(ter)) { return ter; // LCOV_EXCL_LINE } - view.update(sleDest); + ctx.view.update(sleDest); } - if (!view.exists(trustLineKey) && !receiverIssuer) + if (!ctx.view.exists(trustLineKey) && !receiverIssuer) return tecNO_LINE; - auto const xferRate = transferRate(view, amount); + auto const xferRate = transferRate(ctx.view, amount); // update if issuer rate is less than locked rate if (xferRate < lockedRate) lockedRate = xferRate; @@ -137,7 +147,7 @@ escrowUnlockApplyHelper( // of the funds if (!createAsset) { - auto const sleRippleState = view.peek(trustLineKey); + auto const sleRippleState = ctx.view.peek(trustLineKey); if (!sleRippleState) return tecINTERNAL; // LCOV_EXCL_LINE @@ -163,7 +173,7 @@ escrowUnlockApplyHelper( // if destination is not the issuer then transfer funds if (!receiverIssuer) { - auto const ter = directSendNoFee(view, issuer, receiver, finalAmt, true, journal); + auto const ter = directSendNoFee(ctx.view, issuer, receiver, finalAmt, true, journal); if (!isTesSuccess(ter)) return ter; // LCOV_EXCL_LINE } @@ -173,10 +183,10 @@ escrowUnlockApplyHelper( template <> inline TER escrowUnlockApplyHelper( - ApplyView& view, + ApplyViewContext ctx, Rate lockedRate, SLE::ref sleDest, - STAmount const& xrpBalance, + XRPAmount xrpBalance, STAmount const& amount, AccountID const& issuer, AccountID const& sender, @@ -189,27 +199,32 @@ escrowUnlockApplyHelper( auto const mptID = amount.get().getMptID(); auto const issuanceKey = keylet::mptokenIssuance(mptID); - if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && createAsset && !receiverIssuer) + auto const mptKeylet = keylet::mptoken(issuanceKey.key, receiver); + if (!ctx.view.exists(mptKeylet) && createAsset && !receiverIssuer) { - if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)}; - xrpBalance < view.fees().accountReserve(ownerCount + 1)) - { - return tecINSUFFICIENT_RESERVE; - } + auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, sleDest); + if (!sponsorSle) + return sponsorSle.error(); // LCOV_EXCL_LINE - if (auto const ter = createMPToken(view, mptID, receiver, 0); !isTesSuccess(ter)) + if (auto const ret = checkReserve( + ctx, sleDest, xrpBalance, *sponsorSle, {.ownerCountDelta = 1}, journal); + !isTesSuccess(ret)) + return ret; + + if (auto const ter = createMPToken(ctx.view, mptID, receiver, *sponsorSle, 0); + !isTesSuccess(ter)) { return ter; // LCOV_EXCL_LINE } // update owner count. - adjustOwnerCount(view, sleDest, 1, journal); + increaseOwnerCount(ctx.view, sleDest, *sponsorSle, 1, journal); } - if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && !receiverIssuer) + if (!ctx.view.exists(mptKeylet) && !receiverIssuer) return tecNO_PERMISSION; - auto const xferRate = transferRate(view, amount); + auto const xferRate = transferRate(ctx.view, amount); // update if issuer rate is less than locked rate if (xferRate < lockedRate) lockedRate = xferRate; @@ -232,11 +247,11 @@ escrowUnlockApplyHelper( finalAmt = amount.value() - xferFee; } return unlockEscrowMPT( - view, + ctx.view, sender, receiver, finalAmt, - view.rules().enabled(fixTokenEscrowV1) ? amount : finalAmt, + ctx.view.rules().enabled(fixTokenEscrowV1) ? amount : finalAmt, journal); } diff --git a/include/xrpl/ledger/helpers/LendingHelpers.h b/include/xrpl/ledger/helpers/LendingHelpers.h index 873abae272..e2605e9ab7 100644 --- a/include/xrpl/ledger/helpers/LendingHelpers.h +++ b/include/xrpl/ledger/helpers/LendingHelpers.h @@ -63,7 +63,9 @@ static constexpr std::uint32_t kSecondsInYear = 365 * 24 * 60 * 60; Number loanPeriodicRate(TenthBips32 interestRate, std::uint32_t paymentInterval); -/// Ensure the periodic payment is always rounded consistently +/** + * Ensure the periodic payment is always rounded consistently + */ inline Number roundPeriodicPayment(Asset const& asset, Number const& periodicPayment, std::int32_t scale) { @@ -127,7 +129,8 @@ struct LoanPaymentParts operator==(LoanPaymentParts const& other) const; }; -/** This structure captures the parts of a loan state. +/** + * This structure captures the parts of a loan state. * * Whether the values are theoretical (unrounded) or rounded will depend on how * it was computed. @@ -324,12 +327,14 @@ struct PaymentComponents // - extra: An additional payment beyond the regular schedule (overpayment) PaymentSpecialCase specialCase = PaymentSpecialCase::None; - // Calculates the tracked interest portion of this payment. - // This is derived from the other components as: - // trackedValueDelta - trackedPrincipalDelta - trackedManagementFeeDelta - // - // @return The amount of tracked interest included in this payment that - // will be paid to the vault. + /** + * Calculates the tracked interest portion of this payment. + * This is derived from the other components as: + * trackedValueDelta - trackedPrincipalDelta - trackedManagementFeeDelta + * + * @return The amount of tracked interest included in this payment that + * will be paid to the vault. + */ [[nodiscard]] Number trackedInterestPart() const; }; @@ -401,7 +406,8 @@ struct LoanStateDeltas // The difference in management fee outstanding between two loan states. Number managementFee; - /* Calculates the total change across all components. + /** + * Calculates the total change across all components. * @return The sum of principal, interest, and management fee deltas. */ [[nodiscard]] Number diff --git a/include/xrpl/ledger/helpers/MPTokenHelpers.h b/include/xrpl/ledger/helpers/MPTokenHelpers.h index a725871231..5418e5b26a 100644 --- a/include/xrpl/ledger/helpers/MPTokenHelpers.h +++ b/include/xrpl/ledger/helpers/MPTokenHelpers.h @@ -29,15 +29,17 @@ namespace xrpl { [[nodiscard]] bool isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue); -/** Returns true if @p account's MPToken for @p mptIssue carries the - * individual-lock flag (lsfMPTLocked). +/** + * Returns true if @p account's MPToken for @p mptIssue carries the + * individual-lock flag (lsfMPTLocked). * - * @warning This checks only the raw per-holder lock bit. It does **not** - * perform the transitive vault pseudo-account check: if @p mptIssue is a - * vault share whose underlying asset is frozen, this function returns false. - * Call @ref isFrozen instead when determining whether an account may send or - * receive tokens — it combines isIndividualFrozen, isGlobalFrozen, and - * isVaultPseudoAccountFrozen into a single complete check. */ + * @warning This checks only the raw per-holder lock bit. It does **not** + * perform the transitive vault pseudo-account check: if @p mptIssue is a + * vault share whose underlying asset is frozen, this function returns false. + * Call @ref isFrozen instead when determining whether an account may send or + * receive tokens — it combines isIndividualFrozen, isGlobalFrozen, and + * isVaultPseudoAccountFrozen into a single complete check. + */ [[nodiscard]] bool isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue); @@ -61,7 +63,8 @@ isAnyFrozen( // //------------------------------------------------------------------------------ -/** Returns MPT transfer fee as Rate. Rate specifies +/** + * Returns MPT transfer fee as Rate. Rate specifies * the fee as fractions of 1 billion. For example, 1% transfer rate * is represented as 1,010,000,000. * @param issuanceID MPTokenIssuanceID of MPTTokenIssuance object @@ -86,7 +89,7 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue); [[nodiscard]] TER authorizeMPToken( - ApplyView& view, + ApplyViewContext ctx, XRPAmount const& priorBalance, MPTID const& mptIssuanceID, AccountID const& account, @@ -94,7 +97,8 @@ authorizeMPToken( std::uint32_t flags = 0, std::optional holderID = std::nullopt); -/** Check if the account lacks required authorization for MPT. +/** + * Check if the account lacks required authorization for MPT. * * requireAuth check is recursive for MPT shares in a vault, descending to * assets in the vault, up to maxAssetCheckDepth recursion depth. This is @@ -109,7 +113,8 @@ requireAuth( AuthType authType = AuthType::Legacy, std::uint8_t depth = 0); -/** Enforce account has MPToken to match its authorization. +/** + * Enforce account has MPToken to match its authorization. * * Called from doApply - it will check for expired (and delete if found any) * credentials matching DomainID set in MPTokenIssuance. Must be called if @@ -117,50 +122,52 @@ requireAuth( */ [[nodiscard]] TER enforceMPTokenAuthorization( - ApplyView& view, + ApplyViewContext ctx, MPTID const& mptIssuanceID, AccountID const& account, XRPAmount const& priorBalance, beast::Journal j); -/** Resolve the underlying asset of a vault share. +/** + * Resolve the underlying asset of a vault share. * - * Reads sfReferenceHolding from @p sleShareIssuance to determine which - * asset the vault wraps. @p sleHolding must be the SLE that - * sfReferenceHolding points to — either an ltMPTOKEN (returns its - * MPTIssue) or an ltRIPPLE_STATE (returns its low/high Issue). + * Reads sfReferenceHolding from @p sleShareIssuance to determine which + * asset the vault wraps. @p sleHolding must be the SLE that + * sfReferenceHolding points to — either an ltMPTOKEN (returns its + * MPTIssue) or an ltRIPPLE_STATE (returns its low/high Issue). * - * @pre Both SLEs must exist and @p sleHolding must be of type ltMPTOKEN - * or ltRIPPLE_STATE. Passing any other type is undefined behaviour. - * @param sleShareIssuance MPTokenIssuance SLE for the vault share token. - * @param sleHolding SLE referenced by sfReferenceHolding. - * @return The underlying Asset (MPTIssue or Issue). + * @pre Both SLEs must exist and @p sleHolding must be of type ltMPTOKEN + * or ltRIPPLE_STATE. Passing any other type is undefined behaviour. + * @param sleShareIssuance MPTokenIssuance SLE for the vault share token. + * @param sleHolding SLE referenced by sfReferenceHolding. + * @return The underlying Asset (MPTIssue or Issue). */ [[nodiscard]] Asset assetOfHolding(SLE const& sleShareIssuance, SLE const& sleHolding); -/** Check whether @p to may receive the given MPT from @p from. +/** + * Check whether @p to may receive the given MPT from @p from. * - * The check passes when any of the following is true: - * - @p waive is WaiveMPTCanTransfer::Yes (recovery-path exemption), or - * - @p from or @p to is the issuer, or - * - lsfMPTCanTransfer is set on the MPTokenIssuance. + * The check passes when any of the following is true: + * - @p waive is WaiveMPTCanTransfer::Yes (recovery-path exemption), or + * - @p from or @p to is the issuer, or + * - lsfMPTCanTransfer is set on the MPTokenIssuance. * - * For vault shares (MPTokenIssuances that carry sfReferenceHolding) the - * check recurses into the underlying asset's transferability. This - * recursion is defensive; vault-of-vault-shares is rejected at vault - * creation, so in practice depth never exceeds 1. + * For vault shares (MPTokenIssuances that carry sfReferenceHolding) the + * check recurses into the underlying asset's transferability. This + * recursion is defensive; vault-of-vault-shares is rejected at vault + * creation, so in practice depth never exceeds 1. * - * @param view Ledger state to read from. - * @param mptIssue The MPT issuance being transferred. - * @param from Sending account. - * @param to Receiving account. - * @param waive WaiveMPTCanTransfer::Yes skips the lsfMPTCanTransfer - * check. Use for recovery paths (e.g. unwinding SAV or - * Lending Protocol positions after an issuer revokes - * transferability). - * @param depth Recursion depth; bounded at kMaxAssetCheckDepth. - * @return tesSUCCESS if the transfer is allowed, tecNO_AUTH otherwise. + * @param view Ledger state to read from. + * @param mptIssue The MPT issuance being transferred. + * @param from Sending account. + * @param to Receiving account. + * @param waive WaiveMPTCanTransfer::Yes skips the lsfMPTCanTransfer + * check. Use for recovery paths (e.g. unwinding SAV or + * Lending Protocol positions after an issuer revokes + * transferability). + * @param depth Recursion depth; bounded at kMaxAssetCheckDepth. + * @return tesSUCCESS if the transfer is allowed, tecNO_AUTH otherwise. */ [[nodiscard]] TER canTransfer( @@ -171,22 +178,24 @@ canTransfer( WaiveMPTCanTransfer waive = WaiveMPTCanTransfer::No, std::uint8_t depth = 0); -/** Check whether @p asset may be traded on the DEX. +/** + * Check whether @p asset may be traded on the DEX. * - * For IOU assets the check delegates to the existing offer/AMM freeze - * logic. For MPT assets it checks lsfMPTCanTrade on the MPTokenIssuance. - * Vault shares recurse into the underlying asset's tradability via - * sfReferenceHolding; depth is bounded at kMaxAssetCheckDepth. + * For IOU assets the check delegates to the existing offer/AMM freeze + * logic. For MPT assets it checks lsfMPTCanTrade on the MPTokenIssuance. + * Vault shares recurse into the underlying asset's tradability via + * sfReferenceHolding; depth is bounded at kMaxAssetCheckDepth. * - * @param view Ledger state to read from. - * @param asset The asset to check. - * @param depth Recursion depth; bounded at kMaxAssetCheckDepth. - * @return tesSUCCESS if trading is allowed, tecNO_PERMISSION otherwise. + * @param view Ledger state to read from. + * @param asset The asset to check. + * @param depth Recursion depth; bounded at kMaxAssetCheckDepth. + * @return tesSUCCESS if trading is allowed, tecNO_PERMISSION otherwise. */ [[nodiscard]] TER canTrade(ReadView const& view, Asset const& asset, std::uint8_t depth = 0); -/** Convenience to combine canTrade/Transfer. Returns tesSUCCESS if Asset is Issue. +/** + * Convenience to combine canTrade/Transfer. Returns tesSUCCESS if Asset is Issue. */ [[nodiscard]] TER canMPTTradeAndTransfer( @@ -203,7 +212,7 @@ canMPTTradeAndTransfer( [[nodiscard]] TER addEmptyHolding( - ApplyView& view, + ApplyViewContext ctx, AccountID const& accountID, XRPAmount priorBalance, MPTIssue const& mptIssue, @@ -211,7 +220,7 @@ addEmptyHolding( [[nodiscard]] TER removeEmptyHolding( - ApplyView& view, + ApplyViewContext ctx, AccountID const& accountID, MPTIssue const& mptIssue, beast::Journal journal); @@ -243,6 +252,7 @@ createMPToken( ApplyView& view, MPTID const& mptIssuanceID, AccountID const& account, + SLE::ref sponsorSle, std::uint32_t const flags); TER @@ -250,6 +260,7 @@ checkCreateMPT( xrpl::ApplyView& view, xrpl::MPTIssue const& mptIssue, xrpl::AccountID const& holder, + SLE::ref sponsorSle, beast::Journal j); //------------------------------------------------------------------------------ @@ -270,7 +281,8 @@ availableMPTAmount(SLE const& sleIssuance); std::int64_t availableMPTAmount(ReadView const& view, MPTID const& mptID); -/** Checks for two types of OutstandingAmount overflow during a send operation. +/** + * Checks for two types of OutstandingAmount overflow during a send operation. * 1. **Direct directSendNoFee (Overflow: No):** A true overflow check when * `OutstandingAmount > MaximumAmount`. This threshold is used for direct * directSendNoFee transactions that bypass the payment engine. @@ -295,7 +307,8 @@ isMPTOverflow( [[nodiscard]] STAmount issuerFundsToSelfIssue(ReadView const& view, MPTIssue const& issue); -/** Facilitate tracking of MPT sold by an issuer owning MPT sell offer. +/** + * Facilitate tracking of MPT sold by an issuer owning MPT sell offer. * See ApplyView::issuerSelfDebitHookMPT(). */ void diff --git a/include/xrpl/ledger/helpers/NFTokenHelpers.h b/include/xrpl/ledger/helpers/NFTokenHelpers.h index 1c4d395fbe..d9d195c559 100644 --- a/include/xrpl/ledger/helpers/NFTokenHelpers.h +++ b/include/xrpl/ledger/helpers/NFTokenHelpers.h @@ -24,19 +24,25 @@ namespace xrpl::nft { -/** Delete up to a specified number of offers from the specified token offer - * directory. */ +/** + * Delete up to a specified number of offers from the specified token offer + * directory. + */ std::size_t removeTokenOffersWithLimit( ApplyView& view, Keylet const& directory, std::size_t maxDeletableOffers); -/** Finds the specified token in the owner's token directory. */ +/** + * Finds the specified token in the owner's token directory. + */ std::optional findToken(ReadView const& view, AccountID const& owner, uint256 const& nftokenID); -/** Finds the token in the owner's token directory. Returns token and page. */ +/** + * Finds the token in the owner's token directory. Returns token and page. + */ struct TokenAndPage { STObject token; @@ -49,33 +55,39 @@ struct TokenAndPage std::optional findTokenAndPage(ApplyView& view, AccountID const& owner, uint256 const& nftokenID); -/** Insert the token in the owner's token directory. */ +/** + * Insert the token in the owner's token directory. + */ TER insertToken(ApplyView& view, AccountID owner, STObject&& nft); -/** Remove the token from the owner's token directory. */ +/** + * Remove the token from the owner's token directory. + */ TER removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID); TER removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, SLE::ref page); -/** Deletes the given token offer. - - An offer is tracked in two separate places: - - The token's 'buy' directory, if it's a buy offer; or - - The token's 'sell' directory, if it's a sell offer; and - - The owner directory of the account that placed the offer. - - The offer also consumes one incremental reserve. +/** + * Deletes the given token offer. + * + * An offer is tracked in two separate places: + * - The token's 'buy' directory, if it's a buy offer; or + * - The token's 'sell' directory, if it's a sell offer; and + * - The owner directory of the account that placed the offer. + * + * The offer also consumes one incremental reserve. */ bool deleteTokenOffer(ApplyView& view, SLE::ref offer); -/** Repairs the links in an NFTokenPage directory. - - Returns true if a repair took place, otherwise false. -*/ +/** + * Repairs the links in an NFTokenPage directory. + * + * Returns true if a repair took place, otherwise false. + */ bool repairNFTokenDirectoryLinks(ApplyView& view, AccountID const& owner); @@ -89,7 +101,9 @@ changeTokenURI( uint256 const& nftokenID, std::optional const& uri); -/** Preflight checks shared by NFTokenCreateOffer and NFTokenMint */ +/** + * Preflight checks shared by NFTokenCreateOffer and NFTokenMint + */ NotTEC tokenOfferCreatePreflight( AccountID const& acctID, @@ -101,7 +115,9 @@ tokenOfferCreatePreflight( std::optional const& owner = std::nullopt, std::uint32_t txFlags = tfSellNFToken); -/** Preclaim checks shared by NFTokenCreateOffer and NFTokenMint */ +/** + * Preclaim checks shared by NFTokenCreateOffer and NFTokenMint + */ TER tokenOfferCreatePreclaim( ReadView const& view, @@ -115,7 +131,9 @@ tokenOfferCreatePreclaim( std::optional const& owner = std::nullopt, std::uint32_t txFlags = tfSellNFToken); -/** doApply implementation shared by NFTokenCreateOffer and NFTokenMint */ +/** + * doApply implementation shared by NFTokenCreateOffer and NFTokenMint + */ TER tokenOfferCreateApply( ApplyView& view, diff --git a/include/xrpl/ledger/helpers/OfferHelpers.h b/include/xrpl/ledger/helpers/OfferHelpers.h index fc863dff0a..524288ea33 100644 --- a/include/xrpl/ledger/helpers/OfferHelpers.h +++ b/include/xrpl/ledger/helpers/OfferHelpers.h @@ -7,18 +7,19 @@ namespace xrpl { -/** Delete an offer. - - Requirements: - The offer must exist. - The caller must have already checked permissions. - - @param view The ApplyView to modify. - @param sle The offer to delete. - @param j Journal for logging. - - @return tesSUCCESS on success, otherwise an error code. -*/ +/** + * Delete an offer. + * + * Requirements: + * The offer must exist. + * The caller must have already checked permissions. + * + * @param view The ApplyView to modify. + * @param sle The offer to delete. + * @param j Journal for logging. + * + * @return tesSUCCESS on success, otherwise an error code. + */ // [[nodiscard]] // nodiscard commented out so Flow, BookTip and others compile. TER offerDelete(ApplyView& view, SLE::ref sle, beast::Journal j); diff --git a/include/xrpl/ledger/helpers/OracleHelpers.h b/include/xrpl/ledger/helpers/OracleHelpers.h new file mode 100644 index 0000000000..da04618e46 --- /dev/null +++ b/include/xrpl/ledger/helpers/OracleHelpers.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include // IWYU pragma: keep +#include + +#include +#include + +namespace xrpl { + +constexpr std::uint32_t kMinOracleReserveCount = 1; +constexpr std::uint32_t kMaxOracleReserveCount = 2; +constexpr std::size_t kOracleReserveCountThreshold = 5; + +template + requires requires(T const& t) { t.size(); } +inline std::uint32_t +calculateOracleReserve(T const& priceDataSeries) +{ + return priceDataSeries.size() > kOracleReserveCountThreshold ? kMaxOracleReserveCount + : kMinOracleReserveCount; +} + +inline std::uint32_t +calculateOracleReserve(SLE::const_ref oracleSle) +{ + return calculateOracleReserve(oracleSle->getFieldArray(sfPriceDataSeries)); +} + +} // namespace xrpl diff --git a/include/xrpl/ledger/helpers/PaymentChannelHelpers.h b/include/xrpl/ledger/helpers/PaymentChannelHelpers.h index 6e8cd17f7f..5e1f590c58 100644 --- a/include/xrpl/ledger/helpers/PaymentChannelHelpers.h +++ b/include/xrpl/ledger/helpers/PaymentChannelHelpers.h @@ -12,37 +12,40 @@ namespace xrpl { -/** Close a payment channel and return its remaining funds to the channel owner. +/** + * Close a payment channel and return its remaining funds to the channel owner. * - * @param slep The SLE for the PayChannel object to close. - * @param view The apply view in which ledger state modifications are made. - * @param key The ledger key identifying the PayChannel entry. - * @param j Journal used for fatal-level diagnostic messages. - * @return tesSUCCESS on success; tefBAD_LEDGER if a directory removal - * fails; tefINTERNAL if the source account SLE cannot be found. + * @param slep The SLE for the PayChannel object to close. + * @param view The apply view in which ledger state modifications are made. + * @param key The ledger key identifying the PayChannel entry. + * @param j Journal used for fatal-level diagnostic messages. + * @return tesSUCCESS on success; tefBAD_LEDGER if a directory removal + * fails; tefINTERNAL if the source account SLE cannot be found. */ TER closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal j); -/** Add two uint32_t values with saturation at UINT32_MAX. +/** + * Add two uint32_t values with saturation at UINT32_MAX. * - * @param rules The current ledger rules used to check amendment status. - * @param lhs Left-hand operand. - * @param rhs Right-hand operand. - * @return @p lhs + @p rhs, saturated at UINT32_MAX when the amendment - * is active. + * @param rules The current ledger rules used to check amendment status. + * @param lhs Left-hand operand. + * @param rhs Right-hand operand. + * @return @p lhs + @p rhs, saturated at UINT32_MAX when the amendment + * is active. */ uint32_t saturatingAdd(Rules const& rules, uint32_t const lhs, uint32_t const rhs); -/** Determine whether a payment channel time field represents an expired time. +/** + * Determine whether a payment channel time field represents an expired time. * - * @param view The apply view providing the parent close time and rules. - * @param timeField The optional expiry timestamp (seconds since the XRP - * Ledger epoch). If empty, the function returns false. - * @return @c true if @p timeField is set and the indicated time is - * in the past relative to the view's parent close time; - * @c false otherwise. + * @param view The apply view providing the parent close time and rules. + * @param timeField The optional expiry timestamp (seconds since the XRP + * Ledger epoch). If empty, the function returns false. + * @return @c true if @p timeField is set and the indicated time is + * in the past relative to the view's parent close time; + * @c false otherwise. */ bool isChannelExpired(ApplyView const& view, std::optional timeField); diff --git a/include/xrpl/ledger/helpers/RippleStateHelpers.h b/include/xrpl/ledger/helpers/RippleStateHelpers.h index ab09b931cc..a0508d074f 100644 --- a/include/xrpl/ledger/helpers/RippleStateHelpers.h +++ b/include/xrpl/ledger/helpers/RippleStateHelpers.h @@ -31,13 +31,14 @@ namespace xrpl { // //------------------------------------------------------------------------------ -/** Calculate the maximum amount of IOUs that an account can hold - @param view the ledger to check against. - @param account the account of interest. - @param issuer the issuer of the IOU. - @param currency the IOU to check. - @return The maximum amount that can be held. -*/ +/** + * Calculate the maximum amount of IOUs that an account can hold + * @param view the ledger to check against. + * @param account the account of interest. + * @param issuer the issuer of the IOU. + * @param currency the IOU to check. + * @return The maximum amount that can be held. + */ /** @{ */ STAmount creditLimit( @@ -50,12 +51,13 @@ IOUAmount creditLimit2(ReadView const& v, AccountID const& acc, AccountID const& iss, Currency const& cur); /** @} */ -/** Returns the amount of IOUs issued by issuer that are held by an account - @param view the ledger to check against. - @param account the account of interest. - @param issuer the issuer of the IOU. - @param currency the IOU to check. -*/ +/** + * Returns the amount of IOUs issued by issuer that are held by an account + * @param view the ledger to check against. + * @param account the account of interest. + * @param issuer the issuer of the IOU. + * @param currency the IOU to check. + */ /** @{ */ STAmount creditBalance( @@ -134,10 +136,11 @@ checkDeepFrozen(ReadView const& view, AccountID const& account, Issue const& iss // //------------------------------------------------------------------------------ -/** Create a trust line - - This can set an initial balance. -*/ +/** + * Create a trust line + * + * This can set an initial balance. + */ [[nodiscard]] TER trustCreate( ApplyView& view, @@ -156,6 +159,7 @@ trustCreate( // Issuer should be the account being set. std::uint32_t uQualityIn, std::uint32_t uQualityOut, + SLE::ref sponsorSle, beast::Journal j); [[nodiscard]] TER @@ -178,6 +182,7 @@ issueIOU( AccountID const& account, STAmount const& amount, Issue const& issue, + SLE::ref sponsorSle, beast::Journal j); [[nodiscard]] TER @@ -194,7 +199,8 @@ redeemIOU( // //------------------------------------------------------------------------------ -/** Check if the account lacks required authorization. +/** + * Check if the account lacks required authorization. * * Return tecNO_AUTH or tecNO_LINE if it does * and tesSUCCESS otherwise. @@ -218,7 +224,8 @@ requireAuth( AccountID const& account, AuthType authType = AuthType::Legacy); -/** Check if the destination account is allowed +/** + * Check if the destination account is allowed * to receive IOU. Return terNO_RIPPLE if rippling is * disabled on both sides and tesSUCCESS otherwise. */ @@ -231,11 +238,13 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc // //------------------------------------------------------------------------------ -/// Any transactors that call addEmptyHolding() in doApply must call -/// canAddHolding() in preflight with the same View and Asset +/** + * Any transactors that call addEmptyHolding() in doApply must call + * canAddHolding() in preflight with the same View and Asset + */ [[nodiscard]] TER addEmptyHolding( - ApplyView& view, + ApplyViewContext ctx, AccountID const& accountID, XRPAmount priorBalance, Issue const& issue, @@ -243,12 +252,13 @@ addEmptyHolding( [[nodiscard]] TER removeEmptyHolding( - ApplyView& view, + ApplyViewContext ctx, AccountID const& accountID, Issue const& issue, beast::Journal journal); -/** Delete trustline to AMM. The passed `sle` must be obtained from a prior +/** + * Delete trustline to AMM. The passed `sle` must be obtained from a prior * call to view.peek(). Fail if neither side of the trustline is AMM or * if ammAccountID is seated and is not one of the trustline's side. */ @@ -259,7 +269,8 @@ deleteAMMTrustLine( std::optional const& ammAccountID, beast::Journal j); -/** Delete AMMs MPToken. The passed `sle` must be obtained from a prior +/** + * Delete AMMs MPToken. The passed `sle` must be obtained from a prior * call to view.peek(). */ [[nodiscard]] TER diff --git a/include/xrpl/ledger/helpers/SponsorHelpers.h b/include/xrpl/ledger/helpers/SponsorHelpers.h new file mode 100644 index 0000000000..98bf419140 --- /dev/null +++ b/include/xrpl/ledger/helpers/SponsorHelpers.h @@ -0,0 +1,204 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace xrpl { + +/** + * Whether the given transaction type may use reserve sponsorship (v1). + * + * Reserve sponsorship is restricted to an explicit allow-list of transaction + * types; all others reject spfSponsorReserve at preflight. + */ +bool +isReserveSponsorAllowed(TxType txType); + +/** + * Whether the transaction's fee is sponsored (sfSponsor present + spfSponsorFee set). + */ +inline bool +isFeeSponsored(STTx const& tx) +{ + return tx.isFieldPresent(sfSponsor) && ((tx.getFieldU32(sfSponsorFlags) & spfSponsorFee) != 0u); +} + +/** + * Whether the transaction's reserve is sponsored (sfSponsor present + spfSponsorReserve set). + */ +inline bool +isReserveSponsored(STTx const& tx) +{ + return tx.isFieldPresent(sfSponsor) && + ((tx.getFieldU32(sfSponsorFlags) & spfSponsorReserve) != 0u); +} + +/** + * Return the AccountID of the transaction's reserve sponsor, or nullopt if unsponsored. + */ +std::optional +getTxReserveSponsorID(STTx const& tx); + +/** + * Return a mutable SLE for the transaction's reserve sponsor account. + * + * @param ctx The apply-view context (view + tx) + * @return The sponsor account SLE, a null pointer if the tx is not + * reserve-sponsored, or tecINTERNAL if the sponsor account cannot + * be loaded (an already-checked invariant). + */ +std::expected +getTxReserveSponsor(ApplyViewContext ctx); + +/** + * Return a read-only SLE for the transaction's reserve sponsor account. + * + * @param view The ledger read view + * @param tx The transaction to inspect + * @return The sponsor account SLE, a null pointer if the tx is not + * reserve-sponsored, or tecINTERNAL if the sponsor account cannot + * be loaded (an already-checked invariant). + */ +std::expected +getTxReserveSponsor(ReadView const& view, STTx const& tx); + +/** + * The transaction's reserve sponsor for the given account, if applicable. + * + * A reserve sponsor only covers the transaction submitter's own objects, so + * this returns the tx reserve sponsor SLE only when accountSle is the tx's own + * (non-pseudo) account; otherwise it returns a null sponsor pointer. This is + * the single source of truth for the "sponsor applies to tx.Account only" rule + * that the sponsor-deriving helper overloads in AccountRootHelpers rely on. + * + * @param ctx The apply-view context (view + tx) + * @param accountSle The account whose sponsor is being resolved + * @return The sponsor SLE (nullptr if unsponsored), or tecINTERNAL if the + * sponsor account cannot be loaded (an already-checked invariant) + */ +[[nodiscard]] std::expected +getEffectiveTxReserveSponsor(ApplyViewContext ctx, SLE::const_ref accountSle); + +/** + * Return the AccountID stored in the given sponsor field of a ledger entry, or nullopt if absent. + */ +std::optional +getLedgerEntryReserveSponsorID(SLE::const_ref sle, SF_ACCOUNT const& field = sfSponsor); + +/** + * Return a mutable SLE for the reserve sponsor recorded on a ledger entry. + * + * Reads the sponsor AccountID from @p field on @p sle and peeks the + * corresponding account root in @p view. + * + * @param view The mutable apply view + * @param sle The ledger entry whose sponsor field is inspected + * @param field The field that holds the sponsor AccountID (defaults to sfSponsor) + * @return The sponsor account SLE, or a null pointer if the entry is unsponsored. + */ +SLE::pointer +getLedgerEntryReserveSponsor( + ApplyView& view, + SLE::const_ref sle, + SF_ACCOUNT const& field = sfSponsor); + +/** + * Stamp a reserve sponsor onto a ledger entry using an explicit sponsor SLE. + * + * Sets @p field on @p sle to the AccountID from @p sponsorSle. A no-op when + * @p sponsorSle is null (unsponsored). For RippleState entries the field must + * be sfHighSponsor or sfLowSponsor; for all other entry types it must be + * sfSponsor. + * + * @param sle The ledger entry to stamp + * @param sponsorSle The sponsor's account root SLE (null → no-op) + * @param field The sponsor field to set (defaults to sfSponsor) + */ +void +addSponsorToLedgerEntry( + SLE::ref sle, + SLE::const_ref sponsorSle, + SF_ACCOUNT const& field = sfSponsor); + +/** + * Stamp the transaction's reserve sponsor onto a newly-created ledger entry. + * + * Equivalent to the overload above, but resolves the sponsor via + * getTxReserveSponsor(ctx) instead of taking it explicitly. A no-op when the + * transaction is not reserve-sponsored. The entry is assumed to be owned by + * the transaction submitter, which is the only account a tx reserve sponsor + * can cover. + */ +void +addSponsorToLedgerEntry(ApplyViewContext ctx, SLE::ref sle, SF_ACCOUNT const& field = sfSponsor); + +/** + * Remove the reserve sponsor field from a ledger entry. + * + * A no-op when @p field is not present on @p sle. For RippleState entries + * the field must be sfHighSponsor or sfLowSponsor; for all other entry types + * it must be sfSponsor. + * + * @param sle The ledger entry to modify + * @param field The sponsor field to clear (defaults to sfSponsor) + */ +void +removeSponsorFromLedgerEntry(SLE::ref sle, SF_ACCOUNT const& field = sfSponsor); + +/** + * Whether @p account is the owner of a ledger entry for sponsorship purposes. + * + * Ownership rules vary by entry type. For RippleState entries the owner is + * whichever side of the trust line holds the reserve. For credentials, the + * owner is the subject once accepted and the issuer before acceptance. + * + * @param view The ledger read view (used for SignerList lookup) + * @param sle The ledger entry whose owner is checked + * @param account The candidate account to match against + * @return true if @p account owns @p sle, false otherwise. + */ +bool +isLedgerEntryOwner(ReadView const& view, SLE const& sle, AccountID const& account); + +/** + * Whether this ledger entry type can have a reserve sponsor attached to it. + */ +bool +isLedgerEntrySupportedBySponsorship(SLE const& sle); + +/** + * Return the number of owner-count units the ledger entry consumes. + * + * Most entries cost 1. Exceptions: Oracles scale with their price-data series + * size, Vaults cost 2 (vault + pseudo-account), and legacy SignerList entries + * (pre-MultiSignReserve) cost 2 + signer count. + */ +std::uint32_t +getLedgerEntryOwnerCount(SLE const& sle); + +/** + * Return the SField used to store the reserve sponsor for @p owner on @p sle. + * + * For most entry types this is sfSponsor. RippleState entries use + * sfHighSponsor or sfLowSponsor depending on which side of the trust line + * @p owner holds. + * + * @param sle The ledger entry + * @param owner The account whose sponsor field is needed + * @return sfHighSponsor, sfLowSponsor, or sfSponsor as appropriate. + */ +SF_ACCOUNT const& +getLedgerEntrySponsorField(SLE const& sle, AccountID const& owner); + +} // namespace xrpl diff --git a/include/xrpl/ledger/helpers/TokenHelpers.h b/include/xrpl/ledger/helpers/TokenHelpers.h index 4725b60e95..9f62fe4a9e 100644 --- a/include/xrpl/ledger/helpers/TokenHelpers.h +++ b/include/xrpl/ledger/helpers/TokenHelpers.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -27,21 +28,30 @@ namespace xrpl { // //------------------------------------------------------------------------------ -/** Controls the treatment of frozen account balances */ +/** + * Controls the treatment of frozen account balances + */ enum class FreezeHandling { IgnoreFreeze, ZeroIfFrozen }; -/** Controls the treatment of unauthorized MPT balances */ +/** + * Controls the treatment of unauthorized MPT balances + */ enum class AuthHandling { IgnoreAuth, ZeroIfUnauthorized }; -/** Controls whether to include the account's full spendable balance */ +/** + * Controls whether to include the account's full spendable balance + */ enum class SpendableHandling { SimpleBalance, FullBalance }; enum class WaiveTransferFee : bool { No = false, Yes }; -/** Controls whether accountSend is allowed to overflow OutstandingAmount **/ +/** + * Controls whether accountSend is allowed to overflow OutstandingAmount * + */ enum class AllowMPTOverflow : bool { No = false, Yes }; -/** Controls whether canTransfer enforces lsfMPTCanTransfer on MPTs. +/** + * Controls whether canTransfer enforces lsfMPTCanTransfer on MPTs. * * Default is No (enforce). Use Yes at call sites that must remain available * even when an MPT issuer has cleared lsfMPTCanTransfer - for example, @@ -80,9 +90,9 @@ isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& checkIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset); /** - * isFrozen check is recursive for MPT shares in a vault, descending to - * assets in the vault, up to maxAssetCheckDepth recursion depth. This is - * purely defensive, as we currently do not allow such vaults to be created. + * isFrozen check is recursive for MPT shares in a vault, descending to + * assets in the vault, up to maxAssetCheckDepth recursion depth. This is + * purely defensive, as we currently do not allow such vaults to be created. */ [[nodiscard]] bool isFrozen( @@ -121,9 +131,9 @@ isDeepFrozen( std::uint8_t depth = 0); /** - * isFrozen check is recursive for MPT shares in a vault, descending to - * assets in the vault, up to maxAssetCheckDepth recursion depth. This is - * purely defensive, as we currently do not allow such vaults to be created. + * isFrozen check is recursive for MPT shares in a vault, descending to + * assets in the vault, up to maxAssetCheckDepth recursion depth. This is + * purely defensive, as we currently do not allow such vaults to be created. */ [[nodiscard]] bool isDeepFrozen( @@ -284,7 +294,8 @@ accountFunds( AuthHandling authHandling, beast::Journal j); -/** Returns the transfer fee as Rate based on the type of token +/** + * Returns the transfer fee as Rate based on the type of token * @param view The ledger view * @param asset The asset being transferred */ @@ -309,7 +320,7 @@ canAddHolding(ReadView const& view, Asset const& asset); [[nodiscard]] TER addEmptyHolding( - ApplyView& view, + ApplyViewContext ctx, AccountID const& accountID, XRPAmount priorBalance, Asset const& asset, @@ -317,7 +328,7 @@ addEmptyHolding( [[nodiscard]] TER removeEmptyHolding( - ApplyView& view, + ApplyViewContext ctx, AccountID const& accountID, Asset const& asset, beast::Journal journal); @@ -356,7 +367,8 @@ canTransfer( // --> bCheckIssuer : normally require issuer to be involved. // [[nodiscard]] // nodiscard commented out so DirectStep.cpp compiles. -/** Calls static directSendNoFeeIOU if saAmount represents Issue. +/** + * Calls static directSendNoFeeIOU if saAmount represents Issue. * Calls static directSendNoFeeMPT if saAmount represents MPTIssue. */ TER @@ -368,7 +380,8 @@ directSendNoFee( bool bCheckIssuer, beast::Journal j); -/** Calls static accountSendIOU if saAmount represents Issue. +/** + * Calls static accountSendIOU if saAmount represents Issue. * Calls static accountSendMPT if saAmount represents MPTIssue. */ [[nodiscard]] TER @@ -378,11 +391,13 @@ accountSend( AccountID const& to, STAmount const& saAmount, beast::Journal j, + SLE::ref sponsorSle = {}, WaiveTransferFee waiveFee = WaiveTransferFee::No, AllowMPTOverflow allowOverflow = AllowMPTOverflow::No); using MultiplePaymentDestinations = std::vector>; -/** Like accountSend, except one account is sending multiple payments (with the +/** + * Like accountSend, except one account is sending multiple payments (with the * same asset!) simultaneously * * Calls static accountSendMultiIOU if saAmount represents Issue. diff --git a/include/xrpl/ledger/helpers/VaultHelpers.h b/include/xrpl/ledger/helpers/VaultHelpers.h index 2344b4de77..1bd1663314 100644 --- a/include/xrpl/ledger/helpers/VaultHelpers.h +++ b/include/xrpl/ledger/helpers/VaultHelpers.h @@ -9,57 +9,63 @@ namespace xrpl { -/** From the perspective of a vault, return the number of shares to give - depositor when they offer a fixed amount of assets. Note, since shares are - MPT, this number is integral and always truncated in this calculation. - - @param vault The vault SLE. - @param issuance The MPTokenIssuance SLE for the vault's shares. - @param assets The amount of assets to convert. - - @return The number of shares, or nullopt on error. -*/ +/** + * From the perspective of a vault, return the number of shares to give + * depositor when they offer a fixed amount of assets. Note, since shares are + * MPT, this number is integral and always truncated in this calculation. + * + * @param vault The vault SLE. + * @param issuance The MPTokenIssuance SLE for the vault's shares. + * @param assets The amount of assets to convert. + * + * @return The number of shares, or nullopt on error. + */ [[nodiscard]] std::optional assetsToSharesDeposit(SLE::const_ref vault, SLE::const_ref issuance, STAmount const& assets); -/** From the perspective of a vault, return the number of assets to take from - depositor when they receive a fixed amount of shares. Note, since shares are - MPT, they are always an integral number. - - @param vault The vault SLE. - @param issuance The MPTokenIssuance SLE for the vault's shares. - @param shares The amount of shares to convert. - - @return The number of assets, or nullopt on error. -*/ +/** + * From the perspective of a vault, return the number of assets to take from + * depositor when they receive a fixed amount of shares. Note, since shares are + * MPT, they are always an integral number. + * + * @param vault The vault SLE. + * @param issuance The MPTokenIssuance SLE for the vault's shares. + * @param shares The amount of shares to convert. + * + * @return The number of assets, or nullopt on error. + */ [[nodiscard]] std::optional sharesToAssetsDeposit(SLE::const_ref vault, SLE::const_ref issuance, STAmount const& shares); -/** Controls whether to truncate shares instead of rounding. */ +/** + * Controls whether to truncate shares instead of rounding. + */ enum class TruncateShares : bool { No = false, Yes = true }; -/** Controls whether the withdraw conversion helpers - (assetsToSharesWithdraw and sharesToAssetsWithdraw) subtract - sfLossUnrealized from sfAssetsTotal before computing the exchange rate. - The default (No) applies the standard discounted rate; Yes is used when - the redeemer is the sole remaining shareholder. -*/ +/** + * Controls whether the withdraw conversion helpers + * (assetsToSharesWithdraw and sharesToAssetsWithdraw) subtract + * sfLossUnrealized from sfAssetsTotal before computing the exchange rate. + * The default (No) applies the standard discounted rate; Yes is used when + * the redeemer is the sole remaining shareholder. + */ enum class WaiveUnrealizedLoss : bool { No = false, Yes = true }; -/** From the perspective of a vault, return the number of shares to demand from - the depositor when they ask to withdraw a fixed amount of assets. Since - shares are MPT this number is integral, and it will be rounded to nearest - unless explicitly requested to be truncated instead. - - @param vault The vault SLE. - @param issuance The MPTokenIssuance SLE for the vault's shares. - @param assets The amount of assets to convert. - @param truncate Whether to truncate instead of rounding. - @param waive Whether to waive the unrealized-loss discount when computing - the exchange rate. - - @return The number of shares, or nullopt on error. -*/ +/** + * From the perspective of a vault, return the number of shares to demand from + * the depositor when they ask to withdraw a fixed amount of assets. Since + * shares are MPT this number is integral, and it will be rounded to nearest + * unless explicitly requested to be truncated instead. + * + * @param vault The vault SLE. + * @param issuance The MPTokenIssuance SLE for the vault's shares. + * @param assets The amount of assets to convert. + * @param truncate Whether to truncate instead of rounding. + * @param waive Whether to waive the unrealized-loss discount when computing + * the exchange rate. + * + * @return The number of shares, or nullopt on error. + */ [[nodiscard]] std::optional assetsToSharesWithdraw( SLE::const_ref vault, @@ -68,18 +74,19 @@ assetsToSharesWithdraw( TruncateShares truncate = TruncateShares::No, WaiveUnrealizedLoss waive = WaiveUnrealizedLoss::No); -/** From the perspective of a vault, return the number of assets to give the - depositor when they redeem a fixed amount of shares. Note, since shares are - MPT, they are always an integral number. - - @param vault The vault SLE. - @param issuance The MPTokenIssuance SLE for the vault's shares. - @param shares The amount of shares to convert. - @param waive Whether to waive (i.e. not subtract) the vault's unrealized - loss when computing the exchange rate. - - @return The number of assets, or nullopt on error. -*/ +/** + * From the perspective of a vault, return the number of assets to give the + * depositor when they redeem a fixed amount of shares. Note, since shares are + * MPT, they are always an integral number. + * + * @param vault The vault SLE. + * @param issuance The MPTokenIssuance SLE for the vault's shares. + * @param shares The amount of shares to convert. + * @param waive Whether to waive (i.e. not subtract) the vault's unrealized + * loss when computing the exchange rate. + * + * @return The number of assets, or nullopt on error. + */ [[nodiscard]] std::optional sharesToAssetsWithdraw( SLE::const_ref vault, @@ -87,15 +94,16 @@ sharesToAssetsWithdraw( STAmount const& shares, WaiveUnrealizedLoss waive = WaiveUnrealizedLoss::No); -/** Returns true iff `account` holds all of the vault's outstanding shares — - i.e. is the sole remaining shareholder. Returns false if the account - holds no shares or fewer than the total outstanding. - - @param view The ledger view. - @param account The candidate sole shareholder. - @param issuance The MPTokenIssuance SLE for the vault's shares; provides - both the share MPTID and the outstanding-amount total. -*/ +/** + * Returns true iff `account` holds all of the vault's outstanding shares — + * i.e. is the sole remaining shareholder. Returns false if the account + * holds no shares or fewer than the total outstanding. + * + * @param view The ledger view. + * @param account The candidate sole shareholder. + * @param issuance The MPTokenIssuance SLE for the vault's shares; provides + * both the share MPTID and the outstanding-amount total. + */ [[nodiscard]] bool isSoleShareholder(ReadView const& view, AccountID const& account, SLE::const_ref issuance); diff --git a/include/xrpl/net/HTTPClient.h b/include/xrpl/net/HTTPClient.h index 7ed9b35b9b..752afac9c4 100644 --- a/include/xrpl/net/HTTPClient.h +++ b/include/xrpl/net/HTTPClient.h @@ -14,7 +14,8 @@ namespace xrpl { -/** Provides an asynchronous HTTP client implementation with optional SSL. +/** + * Provides an asynchronous HTTP client implementation with optional SSL. */ class HTTPClient { @@ -30,14 +31,15 @@ public: bool sslVerify, beast::Journal j); - /** Destroys the global SSL context created by initializeSSLContext(). + /** + * Destroys the global SSL context created by initializeSSLContext(). * - * This releases the underlying boost::asio::ssl::context and any - * associated OpenSSL resources. Must not be called while any - * HTTPClient requests are in flight. + * This releases the underlying boost::asio::ssl::context and any + * associated OpenSSL resources. Must not be called while any + * HTTPClient requests are in flight. * - * @note Currently only called from tests during teardown. In production, - * the SSL context lives for the lifetime of the process. + * @note Currently only called from tests during teardown. In production, + * the SSL context lives for the lifetime of the process. */ static void cleanupSSLContext(); diff --git a/include/xrpl/net/RegisterSSLCerts.h b/include/xrpl/net/RegisterSSLCerts.h index 5cc9934638..004f893515 100644 --- a/include/xrpl/net/RegisterSSLCerts.h +++ b/include/xrpl/net/RegisterSSLCerts.h @@ -5,13 +5,14 @@ #include namespace xrpl { -/** Register default SSL certificates. - - Register the system default SSL root certificates. On linux/mac, - this just calls asio's `set_default_verify_paths` to look in standard - operating system locations. On windows, it uses the OS certificate - store accessible via CryptoAPI. -*/ +/** + * Register default SSL certificates. + * + * Register the system default SSL root certificates. On linux/mac, + * this just calls asio's `set_default_verify_paths` to look in standard + * operating system locations. On windows, it uses the OS certificate + * store accessible via CryptoAPI. + */ void registerSSLCerts(boost::asio::ssl::context&, boost::system::error_code&, beast::Journal j); diff --git a/include/xrpl/nodestore/Backend.h b/include/xrpl/nodestore/Backend.h index 29c4a8b526..564a874c5e 100644 --- a/include/xrpl/nodestore/Backend.h +++ b/include/xrpl/nodestore/Backend.h @@ -15,33 +15,37 @@ namespace xrpl::NodeStore { -/** A backend used for the NodeStore. - - The NodeStore uses a swappable backend so that other database systems - can be tried. Different databases may offer various features such - as improved performance, fault tolerant or distributed storage, or - all in-memory operation. - - A given instance of a backend is fixed to a particular key size. -*/ +/** + * A backend used for the NodeStore. + * + * The NodeStore uses a swappable backend so that other database systems + * can be tried. Different databases may offer various features such + * as improved performance, fault tolerant or distributed storage, or + * all in-memory operation. + * + * A given instance of a backend is fixed to a particular key size. + */ class Backend { public: - /** Destroy the backend. - - All open files are closed and flushed. If there are batched writes - or other tasks scheduled, they will be completed before this call - returns. - */ + /** + * Destroy the backend. + * + * All open files are closed and flushed. If there are batched writes + * or other tasks scheduled, they will be completed before this call + * returns. + */ virtual ~Backend() = default; - /** Get the human-readable name of this backend. - This is used for diagnostic output. - */ + /** + * Get the human-readable name of this backend. + * This is used for diagnostic output. + */ virtual std::string getName() = 0; - /** Get the block size for backends that support it + /** + * Get the block size for backends that support it */ [[nodiscard]] virtual std::optional getBlockSize() const @@ -49,25 +53,28 @@ public: return std::nullopt; } - /** Open the backend. - @param createIfMissing Create the database files if necessary. - This allows the caller to catch exceptions. - */ + /** + * Open the backend. + * @param createIfMissing Create the database files if necessary. + * This allows the caller to catch exceptions. + */ virtual void open(bool createIfMissing = true) = 0; - /** Returns true is the database is open. + /** + * Returns true is the database is open. */ virtual bool isOpen() = 0; - /** Open the backend. - @param createIfMissing Create the database files if necessary. - @param appType Deterministic appType used to create a backend. - @param uid Deterministic uid used to create a backend. - @param salt Deterministic salt used to create a backend. - @throws std::runtime_error is function is called not for NuDB backend. - */ + /** + * Open the backend. + * @param createIfMissing Create the database files if necessary. + * @param appType Deterministic appType used to create a backend. + * @param uid Deterministic uid used to create a backend. + * @param salt Deterministic salt used to create a backend. + * @throws std::runtime_error is function is called not for NuDB backend. + */ virtual void open(bool createIfMissing, uint64_t appType, uint64_t uid, uint64_t salt) { @@ -75,60 +82,70 @@ public: "Deterministic appType/uid/salt not supported by backend " + getName()); } - /** Close the backend. - This allows the caller to catch exceptions. - */ + /** + * Close the backend. + * This allows the caller to catch exceptions. + */ virtual void close() = 0; - /** Fetch a single object. - If the object is not found or an error is encountered, the - result will indicate the condition. - @note This will be called concurrently. - @param hash The hash of the object. - @param pObject [out] The created object if successful. - @return The result of the operation. - */ + /** + * Fetch a single object. + * If the object is not found or an error is encountered, the + * result will indicate the condition. + * @note This will be called concurrently. + * @param hash The hash of the object. + * @param pObject [out] The created object if successful. + * @return The result of the operation. + */ virtual Status fetch(uint256 const& hash, std::shared_ptr* pObject) = 0; - /** Store a single object. - Depending on the implementation this may happen immediately - or deferred using a scheduled task. - @note This will be called concurrently. - @param object The object to store. - */ + /** + * Store a single object. + * Depending on the implementation this may happen immediately + * or deferred using a scheduled task. + * @note This will be called concurrently. + * @param object The object to store. + */ virtual void store(std::shared_ptr const& object) = 0; - /** Store a group of objects. - @note This function will not be called concurrently with - itself or @ref store. - */ + /** + * Store a group of objects. + * @note This function will not be called concurrently with + * itself or @ref store. + */ virtual void storeBatch(Batch const& batch) = 0; virtual void sync() = 0; - /** Visit every object in the database - This is usually called during import. - @note This routine will not be called concurrently with itself - or other methods. - @see import - */ + /** + * Visit every object in the database + * This is usually called during import. + * @note This routine will not be called concurrently with itself + * or other methods. + * @see import + */ virtual void forEach(std::function)> f) = 0; - /** Estimate the number of write operations pending. */ + /** + * Estimate the number of write operations pending. + */ virtual int getWriteLoad() = 0; - /** Remove contents on disk upon destruction. */ + /** + * Remove contents on disk upon destruction. + */ virtual void setDeletePath() = 0; - /** Perform consistency checks on database. + /** + * Perform consistency checks on database. * * This method is implemented only by NuDBBackend. It is not yet called * anywhere, but it might be a good idea to one day call it at startup to @@ -139,7 +156,9 @@ public: { } - /** Returns the number of file descriptors the backend expects to need. */ + /** + * Returns the number of file descriptors the backend expects to need. + */ [[nodiscard]] virtual int fdRequired() const = 0; }; diff --git a/include/xrpl/nodestore/Database.h b/include/xrpl/nodestore/Database.h index 49002ee301..96ba91bd76 100644 --- a/include/xrpl/nodestore/Database.h +++ b/include/xrpl/nodestore/Database.h @@ -27,100 +27,109 @@ class Section; namespace xrpl::NodeStore { -/** Persistency layer for NodeObject - - A Node is a ledger object which is uniquely identified by a key, which is - the 256-bit hash of the body of the node. The payload is a variable length - block of serialized data. - - All ledger data is stored as node objects and as such, needs to be persisted - between launches. Furthermore, since the set of node objects will in - general be larger than the amount of available memory, purged node objects - which are later accessed must be retrieved from the node store. - - @see NodeObject -*/ +/** + * Persistency layer for NodeObject + * + * A Node is a ledger object which is uniquely identified by a key, which is + * the 256-bit hash of the body of the node. The payload is a variable length + * block of serialized data. + * + * All ledger data is stored as node objects and as such, needs to be persisted + * between launches. Furthermore, since the set of node objects will in + * general be larger than the amount of available memory, purged node objects + * which are later accessed must be retrieved from the node store. + * + * @see NodeObject + */ class Database { public: Database() = delete; - /** Construct the node store. - - @param scheduler The scheduler to use for performing asynchronous tasks. - @param readThreads The number of asynchronous read threads to create. - @param config The configuration settings - @param journal Destination for logging output. - */ + /** + * Construct the node store. + * + * @param scheduler The scheduler to use for performing asynchronous tasks. + * @param readThreads The number of asynchronous read threads to create. + * @param config The configuration settings + * @param journal Destination for logging output. + */ Database(Scheduler& scheduler, int readThreads, Section const& config, beast::Journal j); - /** Destroy the node store. - All pending operations are completed, pending writes flushed, - and files closed before this returns. - */ + /** + * Destroy the node store. + * All pending operations are completed, pending writes flushed, + * and files closed before this returns. + */ virtual ~Database(); - /** Retrieve the name associated with this backend. - This is used for diagnostics and may not reflect the actual path - or paths used by the underlying backend. - */ + /** + * Retrieve the name associated with this backend. + * This is used for diagnostics and may not reflect the actual path + * or paths used by the underlying backend. + */ virtual std::string getName() const = 0; - /** Import objects from another database. */ + /** + * Import objects from another database. + */ virtual void importDatabase(Database& source) = 0; - /** Retrieve the estimated number of pending write operations. - This is used for diagnostics. - */ + /** + * Retrieve the estimated number of pending write operations. + * This is used for diagnostics. + */ virtual std::int32_t getWriteLoad() const = 0; - /** Store the object. - - The caller's Blob parameter is overwritten. - - @param type The type of object. - @param data The payload of the object. The caller's - variable is overwritten. - @param hash The 256-bit hash of the payload data. - @param ledgerSeq The sequence of the ledger the object belongs to. - - @return `true` if the object was stored? - */ + /** + * Store the object. + * + * The caller's Blob parameter is overwritten. + * + * @param type The type of object. + * @param data The payload of the object. The caller's + * variable is overwritten. + * @param hash The 256-bit hash of the payload data. + * @param ledgerSeq The sequence of the ledger the object belongs to. + * + * @return `true` if the object was stored? + */ virtual void store(NodeObjectType type, Blob&& data, uint256 const& hash, std::uint32_t ledgerSeq) = 0; - /* Check if two ledgers are in the same database - - If these two sequence numbers map to the same database, - the result of a fetch with either sequence number would - be identical. - - @param s1 The first sequence number - @param s2 The second sequence number - - @return 'true' if both ledgers would be in the same DB - - */ + /** + * Check if two ledgers are in the same database + * + * If these two sequence numbers map to the same database, + * the result of a fetch with either sequence number would + * be identical. + * + * @param s1 The first sequence number + * @param s2 The second sequence number + * + * @return 'true' if both ledgers would be in the same DB + */ virtual bool isSameDB(std::uint32_t s1, std::uint32_t s2) = 0; virtual void sync() = 0; - /** Fetch a node object. - If the object is known to be not in the database, isn't found in the - database during the fetch, or failed to load correctly during the fetch, - `nullptr` is returned. - - @note This can be called concurrently. - @param hash The key of the object to retrieve. - @param ledgerSeq The sequence of the ledger where the object is stored. - @param fetchType the type of fetch, synchronous or asynchronous. - @return The object, or nullptr if it couldn't be retrieved. - */ + /** + * Fetch a node object. + * If the object is known to be not in the database, isn't found in the + * database during the fetch, or failed to load correctly during the fetch, + * `nullptr` is returned. + * + * @note This can be called concurrently. + * @param hash The key of the object to retrieve. + * @param ledgerSeq The sequence of the ledger where the object is stored. + * @param fetchType the type of fetch, synchronous or asynchronous. + * @return The object, or nullptr if it couldn't be retrieved. + */ std::shared_ptr fetchNodeObject( uint256 const& hash, @@ -128,29 +137,33 @@ public: FetchType fetchType = FetchType::Synchronous, bool duplicate = false); - /** Fetch an object without waiting. - If I/O is required to determine whether or not the object is present, - `false` is returned. Otherwise, `true` is returned and `object` is set - to refer to the object, or `nullptr` if the object is not present. - If I/O is required, the I/O is scheduled and `true` is returned - - @note This can be called concurrently. - @param hash The key of the object to retrieve - @param ledgerSeq The sequence of the ledger where the - object is stored. - @param callback Callback function when read completes - */ + /** + * Fetch an object without waiting. + * If I/O is required to determine whether or not the object is present, + * `false` is returned. Otherwise, `true` is returned and `object` is set + * to refer to the object, or `nullptr` if the object is not present. + * If I/O is required, the I/O is scheduled and `true` is returned + * + * @note This can be called concurrently. + * @param hash The key of the object to retrieve + * @param ledgerSeq The sequence of the ledger where the + * object is stored. + * @param callback Callback function when read completes + */ virtual void asyncFetch( uint256 const& hash, std::uint32_t ledgerSeq, std::function const&)>&& callback); - /** Remove expired entries from the positive and negative caches. */ + /** + * Remove expired entries from the positive and negative caches. + */ virtual void sweep() = 0; - /** Gather statistics pertaining to read and write activities. + /** + * Gather statistics pertaining to read and write activities. * * @param obj Json object reference into which to place counters. */ @@ -187,7 +200,9 @@ public: void getCountsJson(json::Value& obj); - /** Returns the number of file descriptors the database expects to need */ + /** + * Returns the number of file descriptors the database expects to need + */ int fdRequired() const { @@ -200,7 +215,8 @@ public: bool isStopping() const; - /** @return The earliest ledger sequence allowed + /** + * @return The earliest ledger sequence allowed */ [[nodiscard]] std::uint32_t earliestLedgerSeq() const noexcept @@ -277,13 +293,14 @@ private: FetchReport& fetchReport, bool duplicate) = 0; - /** Visit every object in the database - This is usually called during import. - - @note This routine will not be called concurrently with itself - or other methods. - @see import - */ + /** + * Visit every object in the database + * This is usually called during import. + * + * @note This routine will not be called concurrently with itself + * or other methods. + * @see import + */ virtual void forEach(std::function)> f) = 0; diff --git a/include/xrpl/nodestore/DatabaseRotating.h b/include/xrpl/nodestore/DatabaseRotating.h index 69eb31261d..1c5bb0efaf 100644 --- a/include/xrpl/nodestore/DatabaseRotating.h +++ b/include/xrpl/nodestore/DatabaseRotating.h @@ -28,13 +28,14 @@ public: { } - /** Rotates the backends. - - @param newBackend New writable backend - @param f A function executed after the rotation outside of lock. The - values passed to f will be the new backend database names _after_ - rotation. - */ + /** + * Rotates the backends. + * + * @param newBackend New writable backend + * @param f A function executed after the rotation outside of lock. The + * values passed to f will be the new backend database names _after_ + * rotation. + */ virtual void rotate( std::unique_ptr&& newBackend, diff --git a/include/xrpl/nodestore/DummyScheduler.h b/include/xrpl/nodestore/DummyScheduler.h index f626115786..49b0d37462 100644 --- a/include/xrpl/nodestore/DummyScheduler.h +++ b/include/xrpl/nodestore/DummyScheduler.h @@ -5,7 +5,9 @@ namespace xrpl::NodeStore { -/** Simple NodeStore Scheduler that just performs the tasks synchronously. */ +/** + * Simple NodeStore Scheduler that just performs the tasks synchronously. + */ class DummyScheduler : public Scheduler { public: diff --git a/include/xrpl/nodestore/Factory.h b/include/xrpl/nodestore/Factory.h index e79ae3e05d..a18023a8a8 100644 --- a/include/xrpl/nodestore/Factory.h +++ b/include/xrpl/nodestore/Factory.h @@ -16,24 +16,29 @@ class Section; namespace xrpl::NodeStore { -/** Base class for backend factories. */ +/** + * Base class for backend factories. + */ class Factory { public: virtual ~Factory() = default; - /** Retrieve the name of this factory. */ + /** + * Retrieve the name of this factory. + */ [[nodiscard]] virtual std::string getName() const = 0; - /** Create an instance of this factory's backend. - - @param keyBytes The fixed number of bytes per key. - @param parameters A set of key/value configuration pairs. - @param burstSize Backend burst size in bytes. - @param scheduler The scheduler to use for running tasks. - @return A pointer to the Backend object. - */ + /** + * Create an instance of this factory's backend. + * + * @param keyBytes The fixed number of bytes per key. + * @param parameters A set of key/value configuration pairs. + * @param burstSize Backend burst size in bytes. + * @param scheduler The scheduler to use for running tasks. + * @return A pointer to the Backend object. + */ virtual std::unique_ptr createInstance( size_t keyBytes, @@ -42,15 +47,16 @@ public: Scheduler& scheduler, beast::Journal journal) = 0; - /** Create an instance of this factory's backend. - - @param keyBytes The fixed number of bytes per key. - @param parameters A set of key/value configuration pairs. - @param burstSize Backend burst size in bytes. - @param scheduler The scheduler to use for running tasks. - @param context The context used by database. - @return A pointer to the Backend object. - */ + /** + * Create an instance of this factory's backend. + * + * @param keyBytes The fixed number of bytes per key. + * @param parameters A set of key/value configuration pairs. + * @param burstSize Backend burst size in bytes. + * @param scheduler The scheduler to use for running tasks. + * @param context The context used by database. + * @return A pointer to the Backend object. + */ virtual std::unique_ptr createInstance( size_t keyBytes, diff --git a/include/xrpl/nodestore/Manager.h b/include/xrpl/nodestore/Manager.h index f813412846..54d99fe94b 100644 --- a/include/xrpl/nodestore/Manager.h +++ b/include/xrpl/nodestore/Manager.h @@ -12,7 +12,9 @@ namespace xrpl::NodeStore { -/** Singleton for managing NodeStore factories and back ends. */ +/** + * Singleton for managing NodeStore factories and back ends. + */ class Manager { public: @@ -22,26 +24,35 @@ public: Manager& operator=(Manager const&) = delete; - /** Returns the instance of the manager singleton. */ + /** + * Returns the instance of the manager singleton. + */ static Manager& instance(); - /** Add a factory. */ + /** + * Add a factory. + */ virtual void insert(Factory& factory) = 0; - /** Remove a factory. */ + /** + * Remove a factory. + */ virtual void erase(Factory& factory) = 0; - /** Return a pointer to the matching factory if it exists. - @param name The name to match, performed case-insensitive. - @return `nullptr` if a match was not found. - */ + /** + * Return a pointer to the matching factory if it exists. + * @param name The name to match, performed case-insensitive. + * @return `nullptr` if a match was not found. + */ virtual Factory* find(std::string const& name) = 0; - /** Create a backend. */ + /** + * Create a backend. + */ virtual std::unique_ptr makeBackend( Section const& parameters, @@ -49,34 +60,35 @@ public: Scheduler& scheduler, beast::Journal journal) = 0; - /** Construct a NodeStore database. - - The parameters are key value pairs passed to the backend. The - 'type' key must exist, it defines the choice of backend. Most - backends also require a 'path' field. - - Some choices for 'type' are: - HyperLevelDB, LevelDBFactory, SQLite, MDB - - If the fastBackendParameter is omitted or empty, no ephemeral database - is used. If the scheduler parameter is omitted or unspecified, a - synchronous scheduler is used which performs all tasks immediately on - the caller's thread. - - @note If the database cannot be opened or created, an exception is - thrown. - - @param name A diagnostic label for the database. - @param burstSize Backend burst size in bytes. - @param scheduler The scheduler to use for performing asynchronous tasks. - @param readThreads The number of async read threads to create - @param backendParameters The parameter string for the persistent - backend. - @param fastBackendParameters [optional] The parameter string for the - ephemeral backend. - - @return The opened database. - */ + /** + * Construct a NodeStore database. + * + * The parameters are key value pairs passed to the backend. The + * 'type' key must exist, it defines the choice of backend. Most + * backends also require a 'path' field. + * + * Some choices for 'type' are: + * HyperLevelDB, LevelDBFactory, SQLite, MDB + * + * If the fastBackendParameter is omitted or empty, no ephemeral database + * is used. If the scheduler parameter is omitted or unspecified, a + * synchronous scheduler is used which performs all tasks immediately on + * the caller's thread. + * + * @note If the database cannot be opened or created, an exception is + * thrown. + * + * @param name A diagnostic label for the database. + * @param burstSize Backend burst size in bytes. + * @param scheduler The scheduler to use for performing asynchronous tasks. + * @param readThreads The number of async read threads to create + * @param backendParameters The parameter string for the persistent + * backend. + * @param fastBackendParameters [optional] The parameter string for the + * ephemeral backend. + * + * @return The opened database. + */ virtual std::unique_ptr makeDatabase( std::size_t burstSize, diff --git a/include/xrpl/nodestore/NodeObject.h b/include/xrpl/nodestore/NodeObject.h index 3f3b75d5f8..b96d65fa12 100644 --- a/include/xrpl/nodestore/NodeObject.h +++ b/include/xrpl/nodestore/NodeObject.h @@ -12,7 +12,9 @@ namespace xrpl { -/** The types of node objects. */ +/** + * The types of node objects. + */ enum class NodeObjectType : std::uint32_t { Unknown = 0, Ledger = 1, @@ -21,15 +23,16 @@ enum class NodeObjectType : std::uint32_t { Dummy = 512 // an invalid or missing object }; -/** A simple object that the Ledger uses to store entries. - NodeObjects are comprised of a type, a hash, and a blob. - They can be uniquely identified by the hash, which is a half-SHA512 of - the blob. The blob is a variable length block of serialized data. The - type identifies what the blob contains. - - @note No checking is performed to make sure the hash matches the data. - @see SHAMap -*/ +/** + * A simple object that the Ledger uses to store entries. + * NodeObjects are comprised of a type, a hash, and a blob. + * They can be uniquely identified by the hash, which is a half-SHA512 of + * the blob. The blob is a variable length block of serialized data. The + * type identifies what the blob contains. + * + * @note No checking is performed to make sure the hash matches the data. + * @see SHAMap + */ class NodeObject : public CountedObject { public: @@ -48,29 +51,36 @@ public: // This constructor is private, use createObject instead. NodeObject(NodeObjectType type, Blob&& data, uint256 const& hash, PrivateAccess); - /** Create an object from fields. - - The caller's variable is modified during this call. The - underlying storage for the Blob is taken over by the NodeObject. - - @param type The type of object. - @param ledgerIndex The ledger in which this object appears. - @param data A buffer containing the payload. The caller's variable - is overwritten. - @param hash The 256-bit hash of the payload data. - */ + /** + * Create an object from fields. + * + * The caller's variable is modified during this call. The + * underlying storage for the Blob is taken over by the NodeObject. + * + * @param type The type of object. + * @param ledgerIndex The ledger in which this object appears. + * @param data A buffer containing the payload. The caller's variable + * is overwritten. + * @param hash The 256-bit hash of the payload data. + */ static std::shared_ptr createObject(NodeObjectType type, Blob&& data, uint256 const& hash); - /** Returns the type of this object. */ + /** + * Returns the type of this object. + */ [[nodiscard]] NodeObjectType getType() const; - /** Returns the hash of the data. */ + /** + * Returns the hash of the data. + */ [[nodiscard]] uint256 const& getHash() const; - /** Returns the underlying data. */ + /** + * Returns the underlying data. + */ [[nodiscard]] Blob const& getData() const; diff --git a/include/xrpl/nodestore/Scheduler.h b/include/xrpl/nodestore/Scheduler.h index 588ff19bdc..5d93a80eaa 100644 --- a/include/xrpl/nodestore/Scheduler.h +++ b/include/xrpl/nodestore/Scheduler.h @@ -8,7 +8,9 @@ namespace xrpl::NodeStore { enum class FetchType { Synchronous, Async }; -/** Contains information about a fetch operation. */ +/** + * Contains information about a fetch operation. + */ struct FetchReport { explicit FetchReport(FetchType fetchType) : fetchType(fetchType) @@ -20,7 +22,9 @@ struct FetchReport bool wasFound = false; }; -/** Contains information about a batch write operation. */ +/** + * Contains information about a batch write operation. + */ struct BatchWriteReport { explicit BatchWriteReport() = default; @@ -29,36 +33,40 @@ struct BatchWriteReport int writeCount; }; -/** Scheduling for asynchronous backend activity - - For improved performance, a backend has the option of performing writes - in batches. These writes can be scheduled using the provided scheduler - object. - - @see BatchWriter -*/ +/** + * Scheduling for asynchronous backend activity + * + * For improved performance, a backend has the option of performing writes + * in batches. These writes can be scheduled using the provided scheduler + * object. + * + * @see BatchWriter + */ class Scheduler { public: virtual ~Scheduler() = default; - /** Schedules a task. - Depending on the implementation, the task may be invoked either on - the current thread of execution, or an unspecified - implementation-defined foreign thread. - */ + /** + * Schedules a task. + * Depending on the implementation, the task may be invoked either on + * the current thread of execution, or an unspecified + * implementation-defined foreign thread. + */ virtual void scheduleTask(Task& task) = 0; - /** Reports completion of a fetch - Allows the scheduler to monitor the node store's performance - */ + /** + * Reports completion of a fetch + * Allows the scheduler to monitor the node store's performance + */ virtual void onFetch(FetchReport const& report) = 0; - /** Reports the completion of a batch write - Allows the scheduler to monitor the node store's performance - */ + /** + * Reports the completion of a batch write + * Allows the scheduler to monitor the node store's performance + */ virtual void onBatchWrite(BatchWriteReport const& report) = 0; }; diff --git a/include/xrpl/nodestore/Task.h b/include/xrpl/nodestore/Task.h index 0695970a68..59fe648476 100644 --- a/include/xrpl/nodestore/Task.h +++ b/include/xrpl/nodestore/Task.h @@ -2,14 +2,17 @@ namespace xrpl::NodeStore { -/** Derived classes perform scheduled tasks. */ +/** + * Derived classes perform scheduled tasks. + */ struct Task { virtual ~Task() = default; - /** Performs the task. - The call may take place on a foreign thread. - */ + /** + * Performs the task. + * The call may take place on a foreign thread. + */ virtual void performScheduledTask() = 0; }; diff --git a/include/xrpl/nodestore/Types.h b/include/xrpl/nodestore/Types.h index eaee82c99e..872d948a36 100644 --- a/include/xrpl/nodestore/Types.h +++ b/include/xrpl/nodestore/Types.h @@ -18,7 +18,9 @@ static constexpr auto kBatchWritePreallocationSize = 256; // static constexpr auto kBatchWriteLimitSize = 65536; -/** Return codes from Backend operations. */ +/** + * Return codes from Backend operations. + */ enum class Status { Ok = 0, NotFound = 1, @@ -29,7 +31,9 @@ enum class Status { CustomCode = 100 }; -/** A batch of NodeObjects to write at once. */ +/** + * A batch of NodeObjects to write at once. + */ using Batch = std::vector>; } // namespace xrpl::NodeStore diff --git a/include/xrpl/nodestore/detail/BatchWriter.h b/include/xrpl/nodestore/detail/BatchWriter.h index 7fa23bcb3e..b89df0da14 100644 --- a/include/xrpl/nodestore/detail/BatchWriter.h +++ b/include/xrpl/nodestore/detail/BatchWriter.h @@ -11,18 +11,21 @@ namespace xrpl::NodeStore { -/** Batch-writing assist logic. - - The batch writes are performed with a scheduled task. Use of the - class it not required. A backend can implement its own write batching, - or skip write batching if doing so yields a performance benefit. - - @see Scheduler -*/ +/** + * Batch-writing assist logic. + * + * The batch writes are performed with a scheduled task. Use of the + * class it not required. A backend can implement its own write batching, + * or skip write batching if doing so yields a performance benefit. + * + * @see Scheduler + */ class BatchWriter : private Task { public: - /** This callback does the actual writing. */ + /** + * This callback does the actual writing. + */ struct Callback { virtual ~Callback() = default; @@ -35,24 +38,30 @@ public: writeBatch(Batch const& batch) = 0; }; - /** Create a batch writer. */ + /** + * Create a batch writer. + */ BatchWriter(Callback& callback, Scheduler& scheduler); - /** Destroy a batch writer. - - Anything pending in the batch is written out before this returns. - */ + /** + * Destroy a batch writer. + * + * Anything pending in the batch is written out before this returns. + */ ~BatchWriter() override; - /** Store the object. - - This will add to the batch and initiate a scheduled task to - write the batch out. - */ + /** + * Store the object. + * + * This will add to the batch and initiate a scheduled task to + * write the batch out. + */ void store(std::shared_ptr const& object); - /** Get an estimate of the amount of writing I/O pending. */ + /** + * Get an estimate of the amount of writing I/O pending. + */ int getWriteLoad(); diff --git a/include/xrpl/nodestore/detail/DecodedBlob.h b/include/xrpl/nodestore/detail/DecodedBlob.h index b2d5fc9c26..d0cc5e3404 100644 --- a/include/xrpl/nodestore/detail/DecodedBlob.h +++ b/include/xrpl/nodestore/detail/DecodedBlob.h @@ -6,39 +6,46 @@ namespace xrpl::NodeStore { -/** Parsed key/value blob into NodeObject components. - - This will extract the information required to construct a NodeObject. It - also does consistency checking and returns the result, so it is possible - to determine if the data is corrupted without throwing an exception. Not - all forms of corruption are detected so further analysis will be needed - to eliminate false negatives. - - @note This defines the database format of a NodeObject! -*/ +/** + * Parsed key/value blob into NodeObject components. + * + * This will extract the information required to construct a NodeObject. It + * also does consistency checking and returns the result, so it is possible + * to determine if the data is corrupted without throwing an exception. Not + * all forms of corruption are detected so further analysis will be needed + * to eliminate false negatives. + * + * @note This defines the database format of a NodeObject! + */ class DecodedBlob { public: - /** Construct the decoded blob from raw data. */ + /** + * Construct the decoded blob from raw data. + */ DecodedBlob(void const* key, void const* value, int valueBytes); - /** Determine if the decoding was successful. */ + /** + * Determine if the decoding was successful. + */ [[nodiscard]] bool wasOk() const noexcept { return success_; } - /** Create a NodeObject from this data. */ + /** + * Create a NodeObject from this data. + */ std::shared_ptr createObject(); private: - bool success_; + bool success_{false}; void const* key_; - NodeObjectType objectType_; - unsigned char const* objectData_; + NodeObjectType objectType_{NodeObjectType::Unknown}; + unsigned char const* objectData_{nullptr}; int dataBytes_; }; diff --git a/include/xrpl/nodestore/detail/EncodedBlob.h b/include/xrpl/nodestore/detail/EncodedBlob.h index 3982ab1b95..d668cdccd8 100644 --- a/include/xrpl/nodestore/detail/EncodedBlob.h +++ b/include/xrpl/nodestore/detail/EncodedBlob.h @@ -14,47 +14,54 @@ namespace xrpl::NodeStore { -/** Convert a NodeObject from in-memory to database format. - - The (suboptimal) database format consists of: - - - 8 prefix bytes which will typically be 0, but don't assume that's the - case; earlier versions of the code would use these bytes to store the - ledger index either once or twice. - - A single byte denoting the type of the object. - - The payload. - - @note This class is typically instantiated on the stack, so the size of - the object does not matter as much as it normally would since the - allocation is, effectively, free. - - We leverage that fact to preallocate enough memory to handle most - payloads as part of this object, eliminating the need for dynamic - allocation. As of this writing ~94% of objects require fewer than - 1024 payload bytes. +/** + * Convert a NodeObject from in-memory to database format. + * + * The (suboptimal) database format consists of: + * + * - 8 prefix bytes which will typically be 0, but don't assume that's the + * case; earlier versions of the code would use these bytes to store the + * ledger index either once or twice. + * - A single byte denoting the type of the object. + * - The payload. + * + * @note This class is typically instantiated on the stack, so the size of + * the object does not matter as much as it normally would since the + * allocation is, effectively, free. + * + * We leverage that fact to preallocate enough memory to handle most + * payloads as part of this object, eliminating the need for dynamic + * allocation. As of this writing ~94% of objects require fewer than + * 1024 payload bytes. */ class EncodedBlob { - /** The 32-byte key of the serialized object. */ + /** + * The 32-byte key of the serialized object. + */ std::array key_{}; - /** A pre-allocated buffer for the serialized object. - - The buffer is large enough for the 9 byte prefix and at least - 1024 more bytes. The precise size is calculated automatically - at compile time so as to avoid wasting space on padding bytes. + /** + * A pre-allocated buffer for the serialized object. + * + * The buffer is large enough for the 9 byte prefix and at least + * 1024 more bytes. The precise size is calculated automatically + * at compile time so as to avoid wasting space on padding bytes. */ std::array payload_{}; - /** The size of the serialized data. */ + /** + * The size of the serialized data. + */ std::uint32_t size_; - /** A pointer to the serialized data. - - This may point to the pre-allocated buffer (if it is sufficiently - large) or to a dynamically allocated buffer. + /** + * A pointer to the serialized data. + * + * This may point to the pre-allocated buffer (if it is sufficiently + * large) or to a dynamically allocated buffer. */ std::uint8_t* const ptr_; diff --git a/include/xrpl/protocol/AMMCore.h b/include/xrpl/protocol/AMMCore.h index c4fccd029a..a3666c7960 100644 --- a/include/xrpl/protocol/AMMCore.h +++ b/include/xrpl/protocol/AMMCore.h @@ -33,17 +33,20 @@ class STObject; class STAmount; class Rules; -/** Calculate Liquidity Provider Token (LPT) Currency. +/** + * Calculate Liquidity Provider Token (LPT) Currency. */ Currency ammLPTCurrency(Asset const& asset1, Asset const& asset2); -/** Calculate LPT Issue from AMM asset pair. +/** + * Calculate LPT Issue from AMM asset pair. */ Issue ammLPTIssue(Asset const& asset1, Asset const& asset2, AccountID const& ammAccountID); -/** Validate the amount. +/** + * Validate the amount. * If validZero is false and amount is beast::zero then invalid amount. * Return error code if invalid amount. * If pair then validate amount's issue matches one of the pair's issue. @@ -65,17 +68,20 @@ invalidAMMAssetPair( Asset const& asset2, std::optional> const& pair = std::nullopt); -/** Get time slot of the auction slot. +/** + * Get time slot of the auction slot. */ std::optional ammAuctionTimeSlot(std::uint64_t current, STObject const& auctionSlot); -/** Return true if required AMM amendment is enabled +/** + * Return true if required AMM amendment is enabled */ bool ammEnabled(Rules const&); -/** Convert to the fee from the basis points +/** + * Convert to the fee from the basis points * @param tfee trading fee in {0, 1000} * 1 = 1/10bps or 0.001%, 1000 = 1% */ @@ -85,7 +91,8 @@ getFee(std::uint16_t tfee) return Number{tfee} / kAuctionSlotFeeScaleFactor; } -/** Get fee multiplier (1 - tfee) +/** + * Get fee multiplier (1 - tfee) * @tfee trading fee in basis points */ inline Number @@ -94,7 +101,8 @@ feeMult(std::uint16_t tfee) return 1 - getFee(tfee); } -/** Get fee multiplier (1 - tfee / 2) +/** + * Get fee multiplier (1 - tfee / 2) * @tfee trading fee in basis points */ inline Number diff --git a/include/xrpl/protocol/AccountID.h b/include/xrpl/protocol/AccountID.h index a7d49246ca..ab3c5a996b 100644 --- a/include/xrpl/protocol/AccountID.h +++ b/include/xrpl/protocol/AccountID.h @@ -28,43 +28,53 @@ public: } // namespace detail -/** A 160-bit unsigned that uniquely identifies an account. */ +/** + * A 160-bit unsigned that uniquely identifies an account. + */ using AccountID = BaseUInt<160, detail::AccountIDTag>; -/** Convert AccountID to base58 checked string */ +/** + * Convert AccountID to base58 checked string + */ std::string toBase58(AccountID const& v); -/** Parse AccountID from checked, base58 string. - @return std::nullopt if a parse error occurs -*/ +/** + * Parse AccountID from checked, base58 string. + * @return std::nullopt if a parse error occurs + */ template <> std::optional parseBase58(std::string const& s); -/** Compute AccountID from public key. - - The account ID is computed as the 160-bit hash of the - public key data. This excludes the version byte and - guard bytes included in the base58 representation. - -*/ +/** + * Compute AccountID from public key. + * + * The account ID is computed as the 160-bit hash of the + * public key data. This excludes the version byte and + * guard bytes included in the base58 representation. + */ // VFALCO In PublicKey.h for now // AccountID // calcAccountID (PublicKey const& pk); -/** A special account that's used as the "issuer" for XRP. */ +/** + * A special account that's used as the "issuer" for XRP. + */ AccountID const& xrpAccount(); -/** A placeholder for empty accounts. */ +/** + * A placeholder for empty accounts. + */ AccountID const& noAccount(); -/** Convert hex or base58 string to AccountID. - - @return `true` if the parsing was successful. -*/ +/** + * Convert hex or base58 string to AccountID. + * + * @return `true` if the parsing was successful. + */ // DEPRECATED bool toIssuer(AccountID&, std::string const&); @@ -91,17 +101,18 @@ operator<<(std::ostream& os, AccountID const& x) return os; } -/** Initialize the global cache used to map AccountID to base58 conversions. - - The cache is optional and need not be initialized. But because conversion - is expensive (it requires a SHA-256 operation) in most cases the overhead - of the cache is worth the benefit. - - @param count The number of entries the cache should accommodate. Zero will - disable the cache, releasing any memory associated with it. - - @note The function will only initialize the cache the first time it is - invoked. Subsequent invocations do nothing. +/** + * Initialize the global cache used to map AccountID to base58 conversions. + * + * The cache is optional and need not be initialized. But because conversion + * is expensive (it requires a SHA-256 operation) in most cases the overhead + * of the cache is worth the benefit. + * + * @param count The number of entries the cache should accommodate. Zero will + * disable the cache, releasing any memory associated with it. + * + * @note The function will only initialize the cache the first time it is + * invoked. Subsequent invocations do nothing. */ void initAccountIdCache(std::size_t count); diff --git a/include/xrpl/protocol/Asset.h b/include/xrpl/protocol/Asset.h index 2bf24b19fe..8e9c09eb89 100644 --- a/include/xrpl/protocol/Asset.h +++ b/include/xrpl/protocol/Asset.h @@ -62,7 +62,8 @@ private: public: Asset() = default; - /** Conversions to Asset are implicit and conversions to specific issue + /** + * Conversions to Asset are implicit and conversions to specific issue * type are explicit. This design facilitates the use of Asset. */ Asset(Issue const& issue) : issue_(issue) @@ -149,7 +150,8 @@ public: friend constexpr bool operator==(BadAsset const& lhs, Asset const& rhs); - /** Return true if both assets refer to the same currency (regardless of + /** + * Return true if both assets refer to the same currency (regardless of * issuer) or MPT issuance. Otherwise return false. */ friend constexpr bool diff --git a/include/xrpl/protocol/Book.h b/include/xrpl/protocol/Book.h index 476bdba35a..a83eb41b24 100644 --- a/include/xrpl/protocol/Book.h +++ b/include/xrpl/protocol/Book.h @@ -19,10 +19,11 @@ namespace xrpl { -/** Specifies an order book. - The order book is a pair of Issues called in and out. - @see Issue. -*/ +/** + * Specifies an order book. + * The order book is a pair of Issues called in and out. + * @see Issue. + */ class Book final : public CountedObject { public: @@ -60,7 +61,9 @@ hash_append(Hasher& h, Book const& b) Book reversed(Book const& book); -/** Equality comparison. */ +/** + * Equality comparison. + */ /** @{ */ [[nodiscard]] constexpr bool operator==(Book const& lhs, Book const& rhs) @@ -69,14 +72,16 @@ operator==(Book const& lhs, Book const& rhs) } /** @} */ -/** Strict weak ordering. */ +/** + * Strict weak ordering. + */ /** @{ */ [[nodiscard]] constexpr std::weak_ordering operator<=>(Book const& lhs, Book const& rhs) { - if (auto const c{lhs.in <=> rhs.in}; c != 0) + if (auto const c{lhs.in <=> rhs.in}; c != 0) // NOLINT(modernize-use-nullptr) return c; - if (auto const c{lhs.out <=> rhs.out}; c != 0) + if (auto const c{lhs.out <=> rhs.out}; c != 0) // NOLINT(modernize-use-nullptr) return c; // Manually compare optionals diff --git a/include/xrpl/protocol/BuildInfo.h b/include/xrpl/protocol/BuildInfo.h index a60c37e714..18ba20f23c 100644 --- a/include/xrpl/protocol/BuildInfo.h +++ b/include/xrpl/protocol/BuildInfo.h @@ -4,74 +4,83 @@ #include #include -/** Versioning information for this build. */ +/** + * Versioning information for this build. + */ // VFALCO The namespace is deprecated namespace xrpl::BuildInfo { -/** Server version. - Follows the Semantic Versioning Specification: - http://semver.org/ -*/ +/** + * Server version. + * Follows the Semantic Versioning Specification: + * http://semver.org/ + */ std::string const& getVersionString(); -/** Full server version string. - This includes the name of the server. It is used in the peer - protocol hello message and also the headers of some HTTP replies. -*/ +/** + * Full server version string. + * This includes the name of the server. It is used in the peer + * protocol hello message and also the headers of some HTTP replies. + */ std::string const& getFullVersionString(); -/** Encode an arbitrary server software version in a 64-bit integer. - - The general format is: - - ........-........-........-........-........-........-........-........ - XXXXXXXX-XXXXXXXX-YYYYYYYY-YYYYYYYY-YYYYYYYY-YYYYYYYY-YYYYYYYY-YYYYYYYY - - X: 16 bits identifying the particular implementation - Y: 48 bits of data specific to the implementation - - The xrpld-specific format (implementation ID is: 0x18 0x3B) is: - - 00011000-00111011-MMMMMMMM-mmmmmmmm-pppppppp-TTNNNNNN-00000000-00000000 - - M: 8-bit major version (0-255) - m: 8-bit minor version (0-255) - p: 8-bit patch version (0-255) - T: 11 if neither an RC nor a beta - 10 if an RC - 01 if a beta - N: 6-bit rc/beta number (1-63) - - @param the version string - @return the encoded version in a 64-bit integer -*/ +/** + * Encode an arbitrary server software version in a 64-bit integer. + * + * The general format is: + * + * ........-........-........-........-........-........-........-........ + * XXXXXXXX-XXXXXXXX-YYYYYYYY-YYYYYYYY-YYYYYYYY-YYYYYYYY-YYYYYYYY-YYYYYYYY + * + * X: 16 bits identifying the particular implementation + * Y: 48 bits of data specific to the implementation + * + * The xrpld-specific format (implementation ID is: 0x18 0x3B) is: + * + * 00011000-00111011-MMMMMMMM-mmmmmmmm-pppppppp-TTNNNNNN-00000000-00000000 + * + * M: 8-bit major version (0-255) + * m: 8-bit minor version (0-255) + * p: 8-bit patch version (0-255) + * T: 11 if neither an RC nor a beta + * 10 if an RC + * 01 if a beta + * N: 6-bit rc/beta number (1-63) + * + * @param the version string + * @return the encoded version in a 64-bit integer + */ std::uint64_t encodeSoftwareVersion(std::string_view versionStr); -/** Returns this server's version packed in a 64-bit integer. */ +/** + * Returns this server's version packed in a 64-bit integer. + */ std::uint64_t getEncodedVersion(); -/** Check if the encoded software version is an xrpld software version. - - @param version another node's encoded software version - @return true if the version is an xrpld software version, false otherwise -*/ +/** + * Check if the encoded software version is an xrpld software version. + * + * @param version another node's encoded software version + * @return true if the version is an xrpld software version, false otherwise + */ bool isXrpldVersion(std::uint64_t version); -/** Check if the version is newer than the local node's xrpld software - version. - - @param version another node's encoded software version - @return true if the version is newer than the local node's xrpld software - version, false otherwise. - - @note This function only understands version numbers that are generated by - xrpld. Please see the encodeSoftwareVersion() function for detail. -*/ +/** + * Check if the version is newer than the local node's xrpld software + * version. + * + * @param version another node's encoded software version + * @return true if the version is newer than the local node's xrpld software + * version, false otherwise. + * + * @note This function only understands version numbers that are generated by + * xrpld. Please see the encodeSoftwareVersion() function for detail. + */ bool isNewerVersion(std::uint64_t version); diff --git a/include/xrpl/protocol/ConfidentialTransfer.h b/include/xrpl/protocol/ConfidentialTransfer.h index 325117eed4..ecf7970aba 100644 --- a/include/xrpl/protocol/ConfidentialTransfer.h +++ b/include/xrpl/protocol/ConfidentialTransfer.h @@ -28,7 +28,9 @@ namespace xrpl { */ struct ConfidentialRecipient { - /** @brief The recipient's ElGamal public key (size=xrpl::kEcPubKeyLength). */ + /** + * @brief The recipient's ElGamal public key (size=xrpl::kEcPubKeyLength). + */ Slice publicKey; /** @@ -44,10 +46,14 @@ struct ConfidentialRecipient */ struct EcPair { - /** @brief First ElGamal ciphertext component. */ + /** + * @brief First ElGamal ciphertext component. + */ secp256k1_pubkey c1; - /** @brief Second ElGamal ciphertext component. */ + /** + * @brief Second ElGamal ciphertext component. + */ secp256k1_pubkey c2; }; diff --git a/include/xrpl/protocol/ErrorCodes.h b/include/xrpl/protocol/ErrorCodes.h index 38b8bc6d76..8ac7c8c58f 100644 --- a/include/xrpl/protocol/ErrorCodes.h +++ b/include/xrpl/protocol/ErrorCodes.h @@ -147,10 +147,11 @@ enum ErrorCodeI { RpcLast = RpcUnexpectedLedgerType // rpcLAST should always equal the last code. }; -/** Codes returned in the `warnings` array of certain RPC commands. - - These values need to remain stable. -*/ +/** + * Codes returned in the `warnings` array of certain RPC commands. + * + * These values need to remain stable. + */ // Protocol-wide, 50+ files // NOLINTNEXTLINE(cppcoreguidelines-use-enum-class) enum WarningCodeI { @@ -168,7 +169,9 @@ enum WarningCodeI { namespace RPC { -/** Maps an rpc error code to its token, default message, and HTTP status. */ +/** + * Maps an rpc error code to its token, default message, and HTTP status. + */ struct ErrorInfo { // Default ctor needed to produce an empty std::array during constexpr eval. @@ -193,11 +196,15 @@ struct ErrorInfo int httpStatus; }; -/** Returns an ErrorInfo that reflects the error code. */ +/** + * Returns an ErrorInfo that reflects the error code. + */ ErrorInfo const& getErrorInfo(ErrorCodeI code); -/** Add or update the json update to reflect the error code. */ +/** + * Add or update the json update to reflect the error code. + */ /** @{ */ void injectError(ErrorCodeI code, json::Value& json); @@ -206,7 +213,9 @@ void injectError(ErrorCodeI code, std::string const& message, json::Value& json); /** @} */ -/** Returns a new json object that reflects the error code. */ +/** + * Returns a new json object that reflects the error code. + */ /** @{ */ json::Value makeError(ErrorCodeI code); @@ -214,7 +223,9 @@ json::Value makeError(ErrorCodeI code, std::string const& message); /** @} */ -/** Returns a new json object that indicates invalid parameters. */ +/** + * Returns a new json object that indicates invalid parameters. + */ /** @{ */ inline json::Value makeParamError(std::string const& message) @@ -314,17 +325,23 @@ notValidatorError() /** @} */ -/** Returns `true` if the json contains an rpc error specification. */ +/** + * Returns `true` if the json contains an rpc error specification. + */ bool containsError(json::Value const& json); -/** Returns http status that corresponds to the error code. */ +/** + * Returns http status that corresponds to the error code. + */ int errorCodeHttpStatus(ErrorCodeI code); } // namespace RPC -/** Returns a single string with the contents of an RPC error. */ +/** + * Returns a single string with the contents of an RPC error. + */ std::string rpcErrorString(json::Value const& jv); diff --git a/include/xrpl/protocol/Feature.h b/include/xrpl/protocol/Feature.h index 927fde542a..f15b7e2d3f 100644 --- a/include/xrpl/protocol/Feature.h +++ b/include/xrpl/protocol/Feature.h @@ -112,7 +112,9 @@ validFeatureName(auto fn) -> bool enum class VoteBehavior : int { Obsolete = -1, DefaultNo = 0, DefaultYes = 1 }; enum class AmendmentSupport : int { Retired = -1, Supported = 0, Unsupported = 1 }; -/** All amendments libxrpl knows about. */ +/** + * All amendments libxrpl knows about. + */ std::map const& allAmendments(); @@ -152,23 +154,27 @@ static constexpr std::size_t kNumFeatures = #undef XRPL_FEATURE #pragma pop_macro("XRPL_FEATURE") -/** Amendments that this server supports and the default voting behavior. - Whether they are enabled depends on the Rules defined in the validated - ledger */ +/** + * Amendments that this server supports and the default voting behavior. + * Whether they are enabled depends on the Rules defined in the validated + * ledger + */ std::map const& supportedAmendments(); -/** Amendments that this server won't vote for by default. - - This function is only used in unit tests. -*/ +/** + * Amendments that this server won't vote for by default. + * + * This function is only used in unit tests. + */ std::size_t numDownVotedAmendments(); -/** Amendments that this server will vote for by default. - - This function is only used in unit tests. -*/ +/** + * Amendments that this server will vote for by default. + * + * This function is only used in unit tests. + */ std::size_t numUpVotedAmendments(); diff --git a/include/xrpl/protocol/Fees.h b/include/xrpl/protocol/Fees.h index 4e79204f4c..fdfadcd8fd 100644 --- a/include/xrpl/protocol/Fees.h +++ b/include/xrpl/protocol/Fees.h @@ -2,7 +2,6 @@ #include -#include #include namespace xrpl { @@ -11,20 +10,27 @@ namespace xrpl { // This was the reference fee units used in the old fee calculation. inline constexpr std::uint32_t kFeeUnitsDeprecated = 10; -/** Reflects the fee settings for a particular ledger. - - The fees are always the same for any transactions applied - to a ledger. Changes to fees occur in between ledgers. -*/ +/** + * Reflects the fee settings for a particular ledger. + * + * The fees are always the same for any transactions applied + * to a ledger. Changes to fees occur in between ledgers. + */ struct Fees { - /** @brief Cost of a reference transaction in drops. */ + /** + * @brief Cost of a reference transaction in drops. + */ XRPAmount base{0}; - /** @brief Minimum XRP an account must hold to exist on the ledger. */ + /** + * @brief Minimum XRP an account must hold to exist on the ledger. + */ XRPAmount reserve{0}; - /** @brief Additional XRP reserve required per owned ledger object. */ + /** + * @brief Additional XRP reserve required per owned ledger object. + */ XRPAmount increment{0}; explicit Fees() = default; @@ -37,15 +43,16 @@ struct Fees { } - /** Returns the account reserve given the owner count, in drops. - - The reserve is calculated as the reserve base plus - the reserve increment times the number of increments. - */ + /** + * Returns the account reserve given the owner count, in drops. + * + * The reserve is calculated as the reserve base times the number of accounts plus the reserve + * increment times the number of increments. + */ [[nodiscard]] XRPAmount - accountReserve(std::size_t ownerCount) const + accountReserve(std::uint32_t ownerCount, std::uint32_t accountCount) const { - return reserve + ownerCount * increment; + return (reserve * accountCount) + (increment * ownerCount); } }; diff --git a/include/xrpl/protocol/HashPrefix.h b/include/xrpl/protocol/HashPrefix.h index 1b05d450a1..9d4471d05c 100644 --- a/include/xrpl/protocol/HashPrefix.h +++ b/include/xrpl/protocol/HashPrefix.h @@ -17,55 +17,80 @@ makeHashPrefix(char a, char b, char c) } // namespace detail -/** Prefix for hashing functions. - - These prefixes are inserted before the source material used to generate - various hashes. This is done to put each hash in its own "space." This way, - two different types of objects with the same binary data will produce - different hashes. - - Each prefix is a 4-byte value with the last byte set to zero and the first - three bytes formed from the ASCII equivalent of some arbitrary string. For - example "TXN". - - @note Hash prefixes are part of the protocol; you cannot, arbitrarily, - change the type or the value of any of these without causing breakage. -*/ +/** + * Prefix for hashing functions. + * + * These prefixes are inserted before the source material used to generate + * various hashes. This is done to put each hash in its own "space." This way, + * two different types of objects with the same binary data will produce + * different hashes. + * + * Each prefix is a 4-byte value with the last byte set to zero and the first + * three bytes formed from the ASCII equivalent of some arbitrary string. For + * example "TXN". + * + * @note Hash prefixes are part of the protocol; you cannot, arbitrarily, + * change the type or the value of any of these without causing breakage. + */ enum class HashPrefix : std::uint32_t { - /** transaction plus signature to give transaction ID */ + /** + * transaction plus signature to give transaction ID + */ TransactionId = detail::makeHashPrefix('T', 'X', 'N'), - /** transaction plus metadata */ + /** + * transaction plus metadata + */ TxNode = detail::makeHashPrefix('S', 'N', 'D'), - /** account state */ + /** + * account state + */ LeafNode = detail::makeHashPrefix('M', 'L', 'N'), - /** inner node in V1 tree */ + /** + * inner node in V1 tree + */ InnerNode = detail::makeHashPrefix('M', 'I', 'N'), - /** ledger master data for signing */ + /** + * ledger master data for signing + */ LedgerMaster = detail::makeHashPrefix('L', 'W', 'R'), - /** inner transaction to sign */ + /** + * inner transaction to sign + */ TxSign = detail::makeHashPrefix('S', 'T', 'X'), - /** inner transaction to multi-sign */ + /** + * inner transaction to multi-sign + */ TxMultiSign = detail::makeHashPrefix('S', 'M', 'T'), - /** validation for signing */ + /** + * validation for signing + */ Validation = detail::makeHashPrefix('V', 'A', 'L'), - /** proposal for signing */ + /** + * proposal for signing + */ Proposal = detail::makeHashPrefix('P', 'R', 'P'), - /** Manifest */ + /** + * Manifest + */ Manifest = detail::makeHashPrefix('M', 'A', 'N'), - /** Payment Channel Claim */ + /** + * Payment Channel Claim + */ PaymentChannelClaim = detail::makeHashPrefix('C', 'L', 'M'), - /** Batch */ + /** + * Batch + */ Batch = detail::makeHashPrefix('B', 'C', 'H'), }; diff --git a/include/xrpl/protocol/IOUAmount.h b/include/xrpl/protocol/IOUAmount.h index 186ce054f1..060ad3d828 100644 --- a/include/xrpl/protocol/IOUAmount.h +++ b/include/xrpl/protocol/IOUAmount.h @@ -11,16 +11,17 @@ namespace xrpl { -/** Floating point representation of amounts with high dynamic range - - Amounts are stored as a normalized signed mantissa and an exponent. The - range of the normalized exponent is [-96,80] and the range of the absolute - value of the normalized mantissa is [1000000000000000, 9999999999999999]. - - Arithmetic operations can throw std::overflow_error during normalization - if the amount exceeds the largest representable amount, but underflows - will silently truncate to zero. -*/ +/** + * Floating point representation of amounts with high dynamic range + * + * Amounts are stored as a normalized signed mantissa and an exponent. The + * range of the normalized exponent is [-96,80] and the range of the absolute + * value of the normalized mantissa is [1000000000000000, 9999999999999999]. + * + * Arithmetic operations can throw std::overflow_error during normalization + * if the amount exceeds the largest representable amount, but underflows + * will silently truncate to zero. + */ class IOUAmount : private boost::totally_ordered, private boost::additive { private: @@ -29,12 +30,13 @@ private: mantissa_type mantissa_{}; exponent_type exponent_{}; - /** Adjusts the mantissa and exponent to the proper range. - - This can throw if the amount cannot be normalized, or is larger than - the largest value that can be represented as an IOU amount. Amounts - that are too small to be represented normalize to 0. - */ + /** + * Adjusts the mantissa and exponent to the proper range. + * + * This can throw if the amount cannot be normalized, or is larger than + * the largest value that can be represented as an IOU amount. Amounts + * that are too small to be represented normalize to 0. + */ void normalize(); @@ -66,11 +68,15 @@ public: bool operator<(IOUAmount const& other) const; - /** Returns true if the amount is not zero */ + /** + * Returns true if the amount is not zero + */ explicit operator bool() const noexcept; - /** Return the sign of the amount */ + /** + * Return the sign of the amount + */ [[nodiscard]] int signum() const noexcept; diff --git a/include/xrpl/protocol/Indexes.h b/include/xrpl/protocol/Indexes.h index 053a66787f..5a76119649 100644 --- a/include/xrpl/protocol/Indexes.h +++ b/include/xrpl/protocol/Indexes.h @@ -24,74 +24,90 @@ namespace xrpl { class SeqProxy; -/** Keylet computation functions. - - Entries in the ledger are located using 256-bit locators. The locators are - calculated using a wide range of parameters specific to the entry whose - locator we are calculating (e.g. an account's locator is derived from the - account's address, whereas the locator for an offer is derived from the - account and the offer sequence.) - - To enhance type safety during lookup and make the code more robust, we use - keylets, which contain not only the locator of the object but also the type - of the object being referenced. - - These functions each return a type-specific keylet. -*/ +/** + * Keylet computation functions. + * + * Entries in the ledger are located using 256-bit locators. The locators are + * calculated using a wide range of parameters specific to the entry whose + * locator we are calculating (e.g. an account's locator is derived from the + * account's address, whereas the locator for an offer is derived from the + * account and the offer sequence.) + * + * To enhance type safety during lookup and make the code more robust, we use + * keylets, which contain not only the locator of the object but also the type + * of the object being referenced. + * + * These functions each return a type-specific keylet. + */ namespace keylet { -/** AccountID root */ +/** + * AccountID root + */ Keylet account(AccountID const& id) noexcept; -/** The index of the amendment table */ +/** + * The index of the amendment table + */ Keylet const& amendments() noexcept; -/** Any item that can be in an owner dir. */ +/** + * Any item that can be in an owner dir. + */ Keylet child(uint256 const& key) noexcept; -/** The index of the "short" skip list - - The "short" skip list is a node (at a fixed index) that holds the hashes - of ledgers since the last flag ledger. It will contain, at most, 256 hashes. -*/ +/** + * The index of the "short" skip list + * + * The "short" skip list is a node (at a fixed index) that holds the hashes + * of ledgers since the last flag ledger. It will contain, at most, 256 hashes. + */ Keylet const& skip() noexcept; -/** The index of the long skip for a particular ledger range. - - The "long" skip list is a node that holds the hashes of (up to) 256 flag - ledgers. - - It can be used to efficiently skip back to any ledger using only two hops: - the first hop gets the "long" skip list for the ledger it wants to retrieve - and uses it to get the hash of the flag ledger whose short skip list will - contain the hash of the requested ledger. -*/ +/** + * The index of the long skip for a particular ledger range. + * + * The "long" skip list is a node that holds the hashes of (up to) 256 flag + * ledgers. + * + * It can be used to efficiently skip back to any ledger using only two hops: + * the first hop gets the "long" skip list for the ledger it wants to retrieve + * and uses it to get the hash of the flag ledger whose short skip list will + * contain the hash of the requested ledger. + */ Keylet skip(LedgerIndex ledger) noexcept; -/** The (fixed) index of the object containing the ledger fees. */ +/** + * The (fixed) index of the object containing the ledger fees. + */ Keylet const& feeSettings() noexcept; -/** The (fixed) index of the object containing the ledger negativeUNL. */ +/** + * The (fixed) index of the object containing the ledger negativeUNL. + */ Keylet const& negativeUNL() noexcept; -/** The beginning of an order book */ +/** + * The beginning of an order book + */ Keylet book(Book const& b); -/** The index of a trust line for a given currency - - Note that a trustline is *shared* between two accounts (commonly referred - to as the issuer and the holder); if Alice sets up a trust line to Bob for - BTC, and Bob trusts Alice for BTC, here is only a single BTC trust line - between them. -*/ +/** + * The index of a trust line for a given currency + * + * Note that a trustline is *shared* between two accounts (commonly referred + * to as the issuer and the holder); if Alice sets up a trust line to Bob for + * BTC, and Bob trusts Alice for BTC, here is only a single BTC trust line + * between them. + */ /** @{ */ Keylet trustLine(AccountID const& id0, AccountID const& id1, Currency const& currency) noexcept; @@ -103,7 +119,9 @@ trustLine(AccountID const& id, Issue const& issue) noexcept } /** @} */ -/** An offer from an account */ +/** + * An offer from an account + */ /** @{ */ Keylet offer(AccountID const& id, std::uint32_t seq) noexcept; @@ -115,15 +133,21 @@ offer(uint256 const& key) noexcept } /** @} */ -/** The initial directory page for a specific quality */ +/** + * The initial directory page for a specific quality + */ Keylet quality(Keylet const& k, std::uint64_t q) noexcept; -/** The directory for the next lower quality */ +/** + * The directory for the next lower quality + */ Keylet next(Keylet const& k); -/** A ticket belonging to an account */ +/** + * A ticket belonging to an account + */ /** @{ */ Keylet ticket(AccountID const& id, std::uint32_t ticketSeq); @@ -138,11 +162,21 @@ ticket(uint256 const& key) } /** @} */ -/** A SignerList */ +/** + * A SignerList + */ Keylet signerList(AccountID const& account) noexcept; -/** A Check */ +/** + * A Sponsorship + */ +Keylet +sponsorship(AccountID const& sponsor, AccountID const& sponsee) noexcept; + +/** + * A Check + */ /** @{ */ Keylet check(AccountID const& id, std::uint32_t seq) noexcept; @@ -154,7 +188,9 @@ check(uint256 const& key) noexcept } /** @} */ -/** A DepositPreauth */ +/** + * A DepositPreauth + */ /** @{ */ Keylet depositPreauth(AccountID const& owner, AccountID const& preauthorized) noexcept; @@ -173,15 +209,21 @@ depositPreauth(uint256 const& key) noexcept //------------------------------------------------------------------------------ -/** Any ledger entry */ +/** + * Any ledger entry + */ Keylet unchecked(uint256 const& key) noexcept; -/** The root page of an account's directory */ +/** + * The root page of an account's directory + */ Keylet ownerDir(AccountID const& id) noexcept; -/** A page in a directory */ +/** + * A page in a directory + */ /** @{ */ Keylet page(uint256 const& root, std::uint64_t index = 0) noexcept; @@ -194,27 +236,36 @@ page(Keylet const& root, std::uint64_t index = 0) noexcept } /** @} */ -/** An escrow entry */ +/** + * An escrow entry + */ Keylet escrow(AccountID const& src, std::uint32_t seq) noexcept; -/** A PaymentChannel */ +/** + * A PaymentChannel + */ Keylet payChannel(AccountID const& src, AccountID const& dst, std::uint32_t seq) noexcept; -/** NFT page keylets - - Unlike objects whose ledger identifiers are produced by hashing data, - NFT page identifiers are composite identifiers, consisting of the owner's - 160-bit AccountID, followed by a 96-bit value that determines which NFT - tokens are candidates for that page. +/** + * NFT page keylets + * + * Unlike objects whose ledger identifiers are produced by hashing data, + * NFT page identifiers are composite identifiers, consisting of the owner's + * 160-bit AccountID, followed by a 96-bit value that determines which NFT + * tokens are candidates for that page. */ /** @{ */ -/** A keylet for the owner's first possible NFT page. */ +/** + * A keylet for the owner's first possible NFT page. + */ Keylet nftokenPageMin(AccountID const& owner); -/** A keylet for the owner's last possible NFT page. */ +/** + * A keylet for the owner's last possible NFT page. + */ Keylet nftokenPageMax(AccountID const& owner); @@ -222,7 +273,9 @@ Keylet nftokenPage(Keylet const& k, uint256 const& token); /** @} */ -/** An offer from an account to buy or sell an NFT */ +/** + * An offer from an account to buy or sell an NFT + */ Keylet nftokenOffer(AccountID const& owner, std::uint32_t seq); @@ -232,22 +285,30 @@ nftokenOffer(uint256 const& offer) return {ltNFTOKEN_OFFER, offer}; } -/** The directory of buy offers for the specified NFT */ +/** + * The directory of buy offers for the specified NFT + */ Keylet nftBuys(uint256 const& id) noexcept; -/** The directory of sell offers for the specified NFT */ +/** + * The directory of sell offers for the specified NFT + */ Keylet nftSells(uint256 const& id) noexcept; -/** AMM entry */ +/** + * AMM entry + */ Keylet amm(Asset const& issue1, Asset const& issue2) noexcept; Keylet amm(uint256 const& amm) noexcept; -/** A keylet for Delegate object */ +/** + * A keylet for Delegate object + */ Keylet delegate(AccountID const& account, AccountID const& authorizedAccount) noexcept; diff --git a/include/xrpl/protocol/InnerObjectFormats.h b/include/xrpl/protocol/InnerObjectFormats.h index c8312c3701..7364e83cfd 100644 --- a/include/xrpl/protocol/InnerObjectFormats.h +++ b/include/xrpl/protocol/InnerObjectFormats.h @@ -6,14 +6,16 @@ namespace xrpl { -/** Manages the list of known inner object formats. +/** + * Manages the list of known inner object formats. */ class InnerObjectFormats : public KnownFormats { private: - /** Create the object. - This will load the object with all the known inner object formats. - */ + /** + * Create the object. + * This will load the object with all the known inner object formats. + */ InnerObjectFormats(); public: diff --git a/include/xrpl/protocol/Issue.h b/include/xrpl/protocol/Issue.h index 3d556e83eb..5cd8731609 100644 --- a/include/xrpl/protocol/Issue.h +++ b/include/xrpl/protocol/Issue.h @@ -10,9 +10,10 @@ namespace xrpl { -/** A currency issued by an account. - @see Currency, AccountID, Issue, Book -*/ +/** + * A currency issued by an account. + * @see Currency, AccountID, Issue, Book + */ class Issue { public: @@ -70,7 +71,9 @@ hash_append(Hasher& h, Issue const& r) hash_append(h, r.currency, r.account); } -/** Equality comparison. */ +/** + * Equality comparison. + */ /** @{ */ [[nodiscard]] constexpr bool operator==(Issue const& lhs, Issue const& rhs) @@ -79,12 +82,14 @@ operator==(Issue const& lhs, Issue const& rhs) } /** @} */ -/** Strict weak ordering. */ +/** + * Strict weak ordering. + */ /** @{ */ [[nodiscard]] constexpr std::weak_ordering operator<=>(Issue const& lhs, Issue const& rhs) { - if (auto const c{lhs.currency <=> rhs.currency}; c != 0) + if (auto const c{lhs.currency <=> rhs.currency}; c != 0) // NOLINT(modernize-use-nullptr) return c; if (isXRP(lhs.currency)) @@ -96,7 +101,9 @@ operator<=>(Issue const& lhs, Issue const& rhs) //------------------------------------------------------------------------------ -/** Returns an asset specifier that represents XRP. */ +/** + * Returns an asset specifier that represents XRP. + */ inline Issue const& xrpIssue() { @@ -104,7 +111,9 @@ xrpIssue() return kIssue; } -/** Returns an asset specifier that represents no account and currency. */ +/** + * Returns an asset specifier that represents no account and currency. + */ inline Issue const& noIssue() { diff --git a/include/xrpl/protocol/Keylet.h b/include/xrpl/protocol/Keylet.h index 19704e2a11..48d494f564 100644 --- a/include/xrpl/protocol/Keylet.h +++ b/include/xrpl/protocol/Keylet.h @@ -7,14 +7,15 @@ namespace xrpl { class STLedgerEntry; -/** A pair of SHAMap key and LedgerEntryType. - - A Keylet identifies both a key in the state map - and its ledger entry type. - - @note Keylet is a portmanteau of the words key - and LET, an acronym for LedgerEntryType. -*/ +/** + * A pair of SHAMap key and LedgerEntryType. + * + * A Keylet identifies both a key in the state map + * and its ledger entry type. + * + * @note Keylet is a portmanteau of the words key + * and LET, an acronym for LedgerEntryType. + */ struct Keylet { uint256 key; @@ -24,7 +25,9 @@ struct Keylet { } - /** Returns true if the SLE matches the type */ + /** + * Returns true if the SLE matches the type + */ [[nodiscard]] bool check(STLedgerEntry const&) const; }; diff --git a/include/xrpl/protocol/KnownFormats.h b/include/xrpl/protocol/KnownFormats.h index c31e28c37d..385feb2c27 100644 --- a/include/xrpl/protocol/KnownFormats.h +++ b/include/xrpl/protocol/KnownFormats.h @@ -15,18 +15,20 @@ namespace xrpl { -/** Manages a list of known formats. - - Each format has a name, an associated KeyType (typically an enumeration), - and a predefined @ref SOElement. - - @tparam KeyType The type of key identifying the format. -*/ +/** + * Manages a list of known formats. + * + * Each format has a name, an associated KeyType (typically an enumeration), + * and a predefined @ref SOElement. + * + * @tparam KeyType The type of key identifying the format. + */ template class KnownFormats { public: - /** A known format. + /** + * A known format. */ class Item { @@ -46,7 +48,8 @@ public: "KnownFormats KeyType must be integral or enum."); } - /** Retrieve the name of the format. + /** + * Retrieve the name of the format. */ [[nodiscard]] std::string const& getName() const @@ -54,7 +57,8 @@ public: return name_; } - /** Retrieve the transaction type this format represents. + /** + * Retrieve the transaction type this format represents. */ [[nodiscard]] KeyType getType() const @@ -74,32 +78,35 @@ public: KeyType const type_; }; - /** Create the known formats object. - - Derived classes will load the object with all the known formats. - */ + /** + * Create the known formats object. + * + * Derived classes will load the object with all the known formats. + */ private: KnownFormats() : name_(beast::typeName()) { } public: - /** Destroy the known formats object. - - The defined formats are deleted. - */ + /** + * Destroy the known formats object. + * + * The defined formats are deleted. + */ virtual ~KnownFormats() = default; KnownFormats(KnownFormats const&) = delete; KnownFormats& operator=(KnownFormats const&) = delete; - /** Retrieve the type for a format specified by name. - - If the format name is unknown, an exception is thrown. - - @param name The name of the type. - @return The type. - */ + /** + * Retrieve the type for a format specified by name. + * + * If the format name is unknown, an exception is thrown. + * + * @param name The name of the type. + * @return The type. + */ [[nodiscard]] KeyType findTypeByName(std::string const& name) const { @@ -110,7 +117,8 @@ public: name.substr(0, std::min(name.size(), std::size_t(32))) + "'"); } - /** Retrieve a format based on its type. + /** + * Retrieve a format based on its type. */ [[nodiscard]] Item const* findByType(KeyType type) const @@ -135,7 +143,8 @@ public: } protected: - /** Retrieve a format based on its name. + /** + * Retrieve a format based on its name. */ [[nodiscard]] Item const* findByName(std::string const& name) const @@ -146,15 +155,16 @@ protected: return itr->second; } - /** Add a new format. - - @param name The name of this format. - @param type The type of this format. - @param uniqueFields A std::vector of unique fields - @param commonFields A std::vector of common fields - - @return The created format. - */ + /** + * Add a new format. + * + * @param name The name of this format. + * @param type The type of this format. + * @param uniqueFields A std::vector of unique fields + * @param commonFields A std::vector of common fields + * + * @return The created format. + */ Item const& add(char const* name, KeyType type, diff --git a/include/xrpl/protocol/LedgerFormats.h b/include/xrpl/protocol/LedgerFormats.h index 5b8a8cc2c5..7c504f6bdd 100644 --- a/include/xrpl/protocol/LedgerFormats.h +++ b/include/xrpl/protocol/LedgerFormats.h @@ -12,28 +12,29 @@ #include namespace xrpl { -/** Identifiers for on-ledger objects. - - Each ledger object requires a unique type identifier, which is stored within the object itself; - this makes it possible to iterate the entire ledger and determine each object's type and verify - that the object you retrieved from a given hash matches the expected type. - - @warning Since these values are stored inside objects stored on the ledger they are part of the - protocol. - **Changing them should be avoided because without special handling, this will result in a hard - fork.** - - @note Values outside this range may be used internally by the code for various purposes, but - attempting to use such values to identify on-ledger objects will result in an invariant failure. - - @note When retiring types, the specific values should not be removed but should be marked as - [[deprecated]]. This is to avoid accidental reuse of identifiers. - - @todo The C++ language does not enable checking for duplicate values here. - If it becomes possible then we should do this. - - @ingroup protocol -*/ +/** + * Identifiers for on-ledger objects. + * + * Each ledger object requires a unique type identifier, which is stored within the object itself; + * this makes it possible to iterate the entire ledger and determine each object's type and verify + * that the object you retrieved from a given hash matches the expected type. + * + * @warning Since these values are stored inside objects stored on the ledger they are part of the + * protocol. + * **Changing them should be avoided because without special handling, this will result in a hard + * fork.** + * + * @note Values outside this range may be used internally by the code for various purposes, but + * attempting to use such values to identify on-ledger objects will result in an invariant failure. + * + * @note When retiring types, the specific values should not be removed but should be marked as + * [[deprecated]]. This is to avoid accidental reuse of identifiers. + * + * @todo The C++ language does not enable checking for duplicate values here. + * If it becomes possible then we should do this. + * + * @ingroup protocol + */ // Protocol-critical, hundreds of usages // NOLINTNEXTLINE(cppcoreguidelines-use-enum-class) enum LedgerEntryType : std::uint16_t { @@ -49,66 +50,72 @@ enum LedgerEntryType : std::uint16_t { #pragma pop_macro("LEDGER_ENTRY") //--------------------------------------------------------------------------- - /** A special type, matching any ledger entry type. - - The value does not represent a concrete type, but rather is used in contexts where the - specific type of a ledger object is unimportant, unknown or unavailable. - - Objects with this special type cannot be created or stored on the ledger. - - \sa keylet::unchecked - */ + /** + * A special type, matching any ledger entry type. + * + * The value does not represent a concrete type, but rather is used in contexts where the + * specific type of a ledger object is unimportant, unknown or unavailable. + * + * Objects with this special type cannot be created or stored on the ledger. + * + * @see keylet::unchecked + */ ltANY = 0, - /** A special type, matching any ledger type except directory nodes. - - The value does not represent a concrete type, but rather is used in contexts where the - ledger object must not be a directory node but its specific type is otherwise unimportant, - unknown or unavailable. - - Objects with this special type cannot be created or stored on the ledger. - - \sa keylet::child + /** + * A special type, matching any ledger type except directory nodes. + * + * The value does not represent a concrete type, but rather is used in contexts where the + * ledger object must not be a directory node but its specific type is otherwise unimportant, + * unknown or unavailable. + * + * Objects with this special type cannot be created or stored on the ledger. + * + * @see keylet::child */ ltCHILD = 0x1CD2, //--------------------------------------------------------------------------- - /** A legacy, deprecated type. - - \deprecated **This object type is not supported and should not be used.** - Support for this type of object was never implemented. - No objects of this type were ever created. + /** + * A legacy, deprecated type. + * + * @deprecated **This object type is not supported and should not be used.** + * Support for this type of object was never implemented. + * No objects of this type were ever created. */ ltNICKNAME [[deprecated("This object type is not supported and should not be used.")]] = 0x006e, - /** A legacy, deprecated type. - - \deprecated **This object type is not supported and should not be used.** - Support for this type of object was never implemented. - No objects of this type were ever created. + /** + * A legacy, deprecated type. + * + * @deprecated **This object type is not supported and should not be used.** + * Support for this type of object was never implemented. + * No objects of this type were ever created. */ ltCONTRACT [[deprecated("This object type is not supported and should not be used.")]] = 0x0063, - /** A legacy, deprecated type. - - \deprecated **This object type is not supported and should not be used.** - Support for this type of object was never implemented. - No objects of this type were ever created. + /** + * A legacy, deprecated type. + * + * @deprecated **This object type is not supported and should not be used.** + * Support for this type of object was never implemented. + * No objects of this type were ever created. */ ltGENERATOR_MAP [[deprecated("This object type is not supported and should not be used.")]] = 0x0067, }; -/** Ledger object flags. - - These flags are specified in ledger objects and modify their behavior. - - @warning Ledger object flags form part of the protocol. - **Changing them should be avoided because without special handling, this will result in a hard - fork.** - - @ingroup protocol -*/ +/** + * Ledger object flags. + * + * These flags are specified in ledger objects and modify their behavior. + * + * @warning Ledger object flags form part of the protocol. + * **Changing them should be avoided because without special handling, this will result in a hard + * fork.** + * + * @ingroup protocol + */ #pragma push_macro("XMACRO") #pragma push_macro("TO_VALUE") #pragma push_macro("VALUE_TO_MAP") @@ -208,7 +215,11 @@ enum LedgerEntryType : std::uint16_t { LEDGER_OBJECT(Loan, \ LSF_FLAG(lsfLoanDefault, 0x00010000) \ LSF_FLAG(lsfLoanImpaired, 0x00020000) \ - LSF_FLAG(lsfLoanOverpayment, 0x00040000)) /* True, loan allows overpayments */ + LSF_FLAG(lsfLoanOverpayment, 0x00040000)) /* True, loan allows overpayments */ \ + \ + LEDGER_OBJECT(Sponsorship, \ + LSF_FLAG(lsfSponsorshipRequireSignForFee, 0x00010000) \ + LSF_FLAG(lsfSponsorshipRequireSignForReserve, 0x00020000)) // clang-format on @@ -285,14 +296,16 @@ getAllLedgerFlags() //------------------------------------------------------------------------------ -/** Holds the list of known ledger entry formats. +/** + * Holds the list of known ledger entry formats. */ class LedgerFormats : public KnownFormats { private: - /** Create the object. - This will load the object with all the known ledger formats. - */ + /** + * Create the object. + * This will load the object with all the known ledger formats. + */ LedgerFormats(); public: diff --git a/include/xrpl/protocol/LedgerHeader.h b/include/xrpl/protocol/LedgerHeader.h index df8f314c5f..d169e53e2c 100644 --- a/include/xrpl/protocol/LedgerHeader.h +++ b/include/xrpl/protocol/LedgerHeader.h @@ -12,7 +12,9 @@ namespace xrpl { -/** Information about the notional ledger backing the view. */ +/** + * Information about the notional ledger backing the view. + */ struct LedgerHeader { explicit LedgerHeader() = default; @@ -67,15 +69,21 @@ getCloseAgree(LedgerHeader const& info) void addRaw(LedgerHeader const&, Serializer&, bool includeHash = false); -/** Deserialize a ledger header from a byte array. */ +/** + * Deserialize a ledger header from a byte array. + */ LedgerHeader deserializeHeader(Slice data, bool hasHash = false); -/** Deserialize a ledger header (prefixed with 4 bytes) from a byte array. */ +/** + * Deserialize a ledger header (prefixed with 4 bytes) from a byte array. + */ LedgerHeader deserializePrefixedHeader(Slice data, bool hasHash = false); -/** Calculate the hash of a ledger header. */ +/** + * Calculate the hash of a ledger header. + */ uint256 calculateLedgerHash(LedgerHeader const& info); diff --git a/include/xrpl/protocol/LedgerShortcut.h b/include/xrpl/protocol/LedgerShortcut.h index 68c31c4c3c..037621121d 100644 --- a/include/xrpl/protocol/LedgerShortcut.h +++ b/include/xrpl/protocol/LedgerShortcut.h @@ -9,13 +9,19 @@ namespace xrpl { * without needing to specify their exact hash or sequence number. */ enum class LedgerShortcut { - /** The current working ledger (open, not yet closed) */ + /** + * The current working ledger (open, not yet closed) + */ Current, - /** The most recently closed ledger (may not be validated) */ + /** + * The most recently closed ledger (may not be validated) + */ Closed, - /** The most recently validated ledger */ + /** + * The most recently validated ledger + */ Validated }; diff --git a/include/xrpl/protocol/MPTAmount.h b/include/xrpl/protocol/MPTAmount.h index b0070489c8..68a7926256 100644 --- a/include/xrpl/protocol/MPTAmount.h +++ b/include/xrpl/protocol/MPTAmount.h @@ -61,7 +61,9 @@ public: bool operator<(MPTAmount const& other) const; - /** Returns true if the amount is not zero */ + /** + * Returns true if the amount is not zero + */ explicit constexpr operator bool() const noexcept; @@ -70,14 +72,17 @@ public: return value(); } - /** Return the sign of the amount */ + /** + * Return the sign of the amount + */ [[nodiscard]] constexpr int signum() const noexcept; - /** Returns the underlying value. Code SHOULD NOT call this - function unless the type has been abstracted away, - e.g. in a templated function. - */ + /** + * Returns the underlying value. Code SHOULD NOT call this + * function unless the type has been abstracted away, + * e.g. in a templated function. + */ [[nodiscard]] constexpr value_type value() const; @@ -101,14 +106,18 @@ MPTAmount::operator=(beast::Zero) return *this; } -/** Returns true if the amount is not zero */ +/** + * Returns true if the amount is not zero + */ constexpr MPTAmount:: operator bool() const noexcept { return value_ != 0; } -/** Return the sign of the amount */ +/** + * Return the sign of the amount + */ constexpr int MPTAmount::signum() const noexcept { @@ -117,10 +126,11 @@ MPTAmount::signum() const noexcept return (value_ != 0) ? 1 : 0; } -/** Returns the underlying value. Code SHOULD NOT call this - function unless the type has been abstracted away, - e.g. in a templated function. -*/ +/** + * Returns the underlying value. Code SHOULD NOT call this + * function unless the type has been abstracted away, + * e.g. in a templated function. + */ constexpr MPTAmount::value_type MPTAmount::value() const { diff --git a/include/xrpl/protocol/MPTIssue.h b/include/xrpl/protocol/MPTIssue.h index 0c495aa57f..7f473da6a2 100644 --- a/include/xrpl/protocol/MPTIssue.h +++ b/include/xrpl/protocol/MPTIssue.h @@ -82,7 +82,8 @@ operator<=>(MPTIssue const& lhs, MPTIssue const& rhs) return lhs.mptID_ <=> rhs.mptID_; } -/** MPT is a non-native token. +/** + * MPT is a non-native token. */ inline bool isXRP(MPTID const&) diff --git a/include/xrpl/protocol/NFTSyntheticSerializer.h b/include/xrpl/protocol/NFTSyntheticSerializer.h index a1d8bce985..bef05b9a8f 100644 --- a/include/xrpl/protocol/NFTSyntheticSerializer.h +++ b/include/xrpl/protocol/NFTSyntheticSerializer.h @@ -9,10 +9,9 @@ namespace xrpl::RPC { /** - Adds common synthetic fields to transaction-related JSON responses - - @{ + * Adds common synthetic fields to transaction-related JSON responses */ +/** @{ */ void insertNFTSyntheticInJson(json::Value&, std::shared_ptr const&, TxMeta const&); /** @} */ diff --git a/include/xrpl/protocol/NFTokenID.h b/include/xrpl/protocol/NFTokenID.h index f61c6bd5cb..b1b994eabd 100644 --- a/include/xrpl/protocol/NFTokenID.h +++ b/include/xrpl/protocol/NFTokenID.h @@ -12,13 +12,13 @@ namespace xrpl { /** - Add a `nftoken_ids` field to the `meta` output parameter. - The field is only added to successful NFTokenMint, NFTokenAcceptOffer, - and NFTokenCancelOffer transactions. - - Helper functions are not static because they can be used by Clio. - @{ + * Add a `nftoken_ids` field to the `meta` output parameter. + * The field is only added to successful NFTokenMint, NFTokenAcceptOffer, + * and NFTokenCancelOffer transactions. + * + * Helper functions are not static because they can be used by Clio. */ +/** @{ */ bool canHaveNFTokenID(std::shared_ptr const& serializedTx, TxMeta const& transactionMeta); diff --git a/include/xrpl/protocol/NFTokenOfferID.h b/include/xrpl/protocol/NFTokenOfferID.h index c4a80356bf..4810f7932a 100644 --- a/include/xrpl/protocol/NFTokenOfferID.h +++ b/include/xrpl/protocol/NFTokenOfferID.h @@ -11,12 +11,12 @@ namespace xrpl { /** - Add an `offer_id` field to the `meta` output parameter. - The field is only added to successful NFTokenCreateOffer transactions. - - Helper functions are not static because they can be used by Clio. - @{ + * Add an `offer_id` field to the `meta` output parameter. + * The field is only added to successful NFTokenCreateOffer transactions. + * + * Helper functions are not static because they can be used by Clio. */ +/** @{ */ bool canHaveNFTokenOfferID( std::shared_ptr const& serializedTx, diff --git a/include/xrpl/protocol/Protocol.h b/include/xrpl/protocol/Protocol.h index f802cfe058..e83e1c97b6 100644 --- a/include/xrpl/protocol/Protocol.h +++ b/include/xrpl/protocol/Protocol.h @@ -14,64 +14,88 @@ namespace xrpl { -/** Protocol specific constants. - - This information is, implicitly, part of the protocol. - - @note Changing these values without adding code to the - server to detect "pre-change" and "post-change" - will result in a hard fork. - - @ingroup protocol -*/ -/** Smallest legal byte size of a transaction. */ +/** + * Protocol specific constants. + * + * This information is, implicitly, part of the protocol. + * + * @note Changing these values without adding code to the + * server to detect "pre-change" and "post-change" + * will result in a hard fork. + * + * @ingroup protocol + */ +/** + * Smallest legal byte size of a transaction. + */ constexpr std::size_t kTxMinSizeBytes = 32; -/** Largest legal byte size of a transaction. */ +/** + * Largest legal byte size of a transaction. + */ constexpr std::size_t kTxMaxSizeBytes = megabytes(1); -/** The maximum number of unfunded offers to delete at once */ +/** + * The maximum number of unfunded offers to delete at once + */ constexpr std::size_t kUnfundedOfferRemoveLimit = 1000; -/** The maximum number of expired offers to delete at once */ +/** + * The maximum number of expired offers to delete at once + */ constexpr std::size_t kExpiredOfferRemoveLimit = 256; -/** The maximum number of metadata entries allowed in one transaction */ +/** + * The maximum number of metadata entries allowed in one transaction + */ constexpr std::size_t kOversizeMetaDataCap = 5200; -/** The maximum number of entries per directory page */ +/** + * The maximum number of entries per directory page + */ constexpr std::size_t kDirNodeMaxEntries = 32; -/** The maximum number of pages allowed in a directory - - Made obsolete by fixDirectoryLimit amendment. -*/ +/** + * The maximum number of pages allowed in a directory + * + * Made obsolete by fixDirectoryLimit amendment. + */ constexpr std::uint64_t kDirNodeMaxPages = 262144; -/** The maximum number of items in an NFT page */ +/** + * The maximum number of items in an NFT page + */ constexpr std::size_t kDirMaxTokensPerPage = 32; -/** The maximum number of owner directory entries for account to be deletable */ +/** + * The maximum number of owner directory entries for account to be deletable + */ constexpr std::size_t kMaxDeletableDirEntries = 1000; -/** The maximum number of token offers that can be canceled at once */ +/** + * The maximum number of token offers that can be canceled at once + */ constexpr std::size_t kMaxTokenOfferCancelCount = 500; -/** The maximum number of offers in an offer directory for NFT to be burnable */ +/** + * The maximum number of offers in an offer directory for NFT to be burnable + */ constexpr std::size_t kMaxDeletableTokenOfferEntries = 500; -/** The maximum token transfer fee allowed. - - Token transfer fees can range from 0% to 50% and are specified in tenths of - a basis point; that is a value of 1000 represents a transfer fee of 1% and - a value of 10000 represents a transfer fee of 10%. - - Note that for extremely low transfer fees values, it is possible that the - calculated fee will be 0. +/** + * The maximum token transfer fee allowed. + * + * Token transfer fees can range from 0% to 50% and are specified in tenths of + * a basis point; that is a value of 1000 represents a transfer fee of 1% and + * a value of 10000 represents a transfer fee of 10%. + * + * Note that for extremely low transfer fees values, it is possible that the + * calculated fee will be 0. */ constexpr std::uint16_t kMaxTransferFee = 50000; -/** There are 10,000 basis points (bips) in 100%. +/** + * There are 10,000 basis points (bips) in 100%. * * Basis points represent 0.01%. * @@ -116,36 +140,41 @@ tenthBipsOfValue(T value, TenthBips bips) } namespace Lending { -/** The maximum management fee rate allowed by a loan broker in 1/10 bips. - - Valid values are between 0 and 10% inclusive. -*/ +/** + * The maximum management fee rate allowed by a loan broker in 1/10 bips. + * + * Valid values are between 0 and 10% inclusive. + */ constexpr TenthBips16 kMaxManagementFeeRate( unsafeCast(percentageToTenthBips(10).value())); static_assert(kMaxManagementFeeRate == TenthBips16(std::uint16_t(10'000u))); -/** The maximum coverage rate required of a loan broker in 1/10 bips. - - Valid values are between 0 and 100% inclusive. -*/ +/** + * The maximum coverage rate required of a loan broker in 1/10 bips. + * + * Valid values are between 0 and 100% inclusive. + */ constexpr TenthBips32 kMaxCoverRate = percentageToTenthBips(100); static_assert(kMaxCoverRate == TenthBips32(100'000u)); -/** The maximum overpayment fee on a loan in 1/10 bips. -* - Valid values are between 0 and 100% inclusive. -*/ +/** + * The maximum overpayment fee on a loan in 1/10 bips. + * + * Valid values are between 0 and 100% inclusive. + */ constexpr TenthBips32 kMaxOverpaymentFee = percentageToTenthBips(100); static_assert(kMaxOverpaymentFee == TenthBips32(100'000u)); -/** Annualized interest rate of the Loan in 1/10 bips. +/** + * Annualized interest rate of the Loan in 1/10 bips. * * Valid values are between 0 and 100% inclusive. */ constexpr TenthBips32 kMaxInterestRate = percentageToTenthBips(100); static_assert(kMaxInterestRate == TenthBips32(100'000u)); -/** The maximum premium added to the interest rate for late payments on a loan +/** + * The maximum premium added to the interest rate for late payments on a loan * in 1/10 bips. * * Valid values are between 0 and 100% inclusive. @@ -153,7 +182,8 @@ static_assert(kMaxInterestRate == TenthBips32(100'000u)); constexpr TenthBips32 kMaxLateInterestRate = percentageToTenthBips(100); static_assert(kMaxLateInterestRate == TenthBips32(100'000u)); -/** The maximum close interest rate charged for repaying a loan early in 1/10 +/** + * The maximum close interest rate charged for repaying a loan early in 1/10 * bips. * * Valid values are between 0 and 100% inclusive. @@ -161,7 +191,8 @@ static_assert(kMaxLateInterestRate == TenthBips32(100'000u)); constexpr TenthBips32 kMaxCloseInterestRate = percentageToTenthBips(100); static_assert(kMaxCloseInterestRate == TenthBips32(100'000u)); -/** The maximum overpayment interest rate charged on loan overpayments in 1/10 +/** + * The maximum overpayment interest rate charged on loan overpayments in 1/10 * bips. * * Valid values are between 0 and 100% inclusive. @@ -169,7 +200,8 @@ static_assert(kMaxCloseInterestRate == TenthBips32(100'000u)); constexpr TenthBips32 kMaxOverpaymentInterestRate = percentageToTenthBips(100); static_assert(kMaxOverpaymentInterestRate == TenthBips32(100'000u)); -/** LoanPay transaction cost will be one base fee per X combined payments +/** + * LoanPay transaction cost will be one base fee per X combined payments * * The number of payments is estimated based on the Amount paid and the Loan's * Fixed Payment size. Overpayments (indicated with the tfLoanOverpayment flag) @@ -180,7 +212,8 @@ static_assert(kMaxOverpaymentInterestRate == TenthBips32(100'000u)); */ static constexpr int kLoanPaymentsPerFeeIncrement = 5; -/** Maximum number of combined payments that a LoanPay transaction will process +/** + * Maximum number of combined payments that a LoanPay transaction will process * * This limit is enforced during the loan payment process, and thus is not * estimated. If the limit is hit, no further payments or overpayments will be @@ -205,173 +238,267 @@ static constexpr int kLoanPaymentsPerFeeIncrement = 5; static constexpr int kLoanMaximumPaymentsPerTransaction = 100; } // namespace Lending -/** The maximum length of a URI inside an NFT */ +/** + * The maximum length of a URI inside an NFT + */ constexpr std::size_t kMaxTokenUriLength = 256; -/** The maximum length of a Data element inside a DID */ +/** + * The maximum length of a Data element inside a DID + */ constexpr std::size_t kMaxDidDocumentLength = 256; -/** The maximum length of a URI inside a DID */ +/** + * The maximum length of a URI inside a DID + */ constexpr std::size_t kMaxDidUriLength = 256; -/** The maximum length of an Attestation inside a DID */ +/** + * The maximum length of an Attestation inside a DID + */ constexpr std::size_t kMaxDidDataLength = 256; -/** The maximum length of a domain */ +/** + * The maximum length of a domain + */ constexpr std::size_t kMaxDomainLength = 256; -/** The maximum length of a URI inside a Credential */ +/** + * The maximum length of a URI inside a Credential + */ constexpr std::size_t kMaxCredentialUriLength = 256; -/** The maximum length of a CredentialType inside a Credential */ +/** + * The maximum length of a CredentialType inside a Credential + */ constexpr std::size_t kMaxCredentialTypeLength = 64; -/** The maximum number of credentials can be passed in array */ +/** + * The maximum number of credentials can be passed in array + */ constexpr std::size_t kMaxCredentialsArraySize = 8; -/** The maximum number of credentials can be passed in array for permissioned - * domain */ +/** + * The maximum number of credentials can be passed in array for permissioned + * domain + */ constexpr std::size_t kMaxPermissionedDomainCredentialsArraySize = 10; -/** The maximum length of MPTokenMetadata */ +/** + * The maximum length of MPTokenMetadata + */ constexpr std::size_t kMaxMpTokenMetadataLength = 1024; -/** The maximum amount of MPTokenIssuance */ +/** + * The maximum amount of MPTokenIssuance + */ constexpr std::uint64_t kMaxMpTokenAmount = 0x7FFF'FFFF'FFFF'FFFFull; static_assert(Number::kMaxRep >= kMaxMpTokenAmount); -/** The maximum length of Data payload */ +/** + * The maximum length of Data payload + */ constexpr std::size_t kMaxDataPayloadLength = 256; -/** Vault withdrawal policies */ +/** + * Vault withdrawal policies + */ constexpr std::uint8_t kVaultStrategyFirstComeFirstServe = 1; -/** Default IOU scale factor for a Vault */ +/** + * Default IOU scale factor for a Vault + */ constexpr std::uint8_t kVaultDefaultIouScale = 6; -/** Maximum scale factor for a Vault. The number is chosen to ensure that -1 IOU can be always converted to shares. -10^19 > maxMPTokenAmount (2^64-1) > 10^18 */ +/** + * Maximum scale factor for a Vault. The number is chosen to ensure that + * 1 IOU can be always converted to shares. + * 10^19 > maxMPTokenAmount (2^64-1) > 10^18 + */ constexpr std::uint8_t kVaultMaximumIouScale = 18; -/** Maximum recursion depth for vault shares being put as an asset inside - * another vault; counted from 0 */ +/** + * Maximum recursion depth for vault shares being put as an asset inside + * another vault; counted from 0 + */ constexpr std::uint8_t kMaxAssetCheckDepth = 5; -/** A ledger index. */ +/** + * A ledger index. + */ using LedgerIndex = std::uint32_t; constexpr std::uint32_t kFlagLedgerInterval = 256; -/** Returns true if the given ledgerIndex is a voting ledgerIndex */ +/** + * Returns true if the given ledgerIndex is a voting ledgerIndex + */ bool isVotingLedger(LedgerIndex seq); -/** Returns true if the given ledgerIndex is a flag ledgerIndex */ +/** + * Returns true if the given ledgerIndex is a flag ledgerIndex + */ bool isFlagLedger(LedgerIndex seq); -/** A transaction identifier. - The value is computed as the hash of the - canonicalized, serialized transaction object. -*/ +/** + * A transaction identifier. + * The value is computed as the hash of the + * canonicalized, serialized transaction object. + */ using TxID = uint256; -/** The maximum number of trustlines to delete as part of AMM account +/** + * The maximum number of trustlines to delete as part of AMM account * deletion cleanup. */ constexpr std::uint16_t kMaxDeletableAmmTrustLines = 512; -/** The maximum length of a URI inside an Oracle */ +/** + * The maximum length of a URI inside an Oracle + */ constexpr std::size_t kMaxOracleUri = 256; -/** The maximum length of a Provider inside an Oracle */ +/** + * The maximum length of a Provider inside an Oracle + */ constexpr std::size_t kMaxOracleProvider = 256; -/** The maximum size of a data series array inside an Oracle */ +/** + * The maximum size of a data series array inside an Oracle + */ constexpr std::size_t kMaxOracleDataSeries = 10; -/** The maximum length of a SymbolClass inside an Oracle */ +/** + * The maximum length of a SymbolClass inside an Oracle + */ constexpr std::size_t kMaxOracleSymbolClass = 16; -/** The maximum allowed time difference between lastUpdateTime and the time - of the last closed ledger -*/ +/** + * The maximum allowed time difference between lastUpdateTime and the time + * of the last closed ledger + */ constexpr std::size_t kMaxLastUpdateTimeDelta = 300; -/** The maximum price scaling factor +/** + * The maximum price scaling factor */ constexpr std::size_t kMaxPriceScale = 20; -/** The maximum percentage of outliers to trim +/** + * The maximum percentage of outliers to trim */ constexpr std::size_t kMaxTrim = 25; -/** The maximum number of delegate permissions an account can grant +/** + * The maximum number of delegate permissions an account can grant */ constexpr std::size_t kPermissionMaxSize = 10; -/** The maximum number of transactions that can be in a batch. */ +/** + * The maximum number of transactions that can be in a batch. + */ constexpr std::size_t kMaxBatchTxCount = 8; -/** The maximum number of batch signers. */ +/** + * The maximum number of batch signers. + */ constexpr std::size_t kMaxBatchSigners = kMaxBatchTxCount * 3; -/** Length of a secp256k1 scalar in bytes. */ +/** + * Length of a secp256k1 scalar in bytes. + */ constexpr std::size_t kEcScalarLength = kMPT_SCALAR_SIZE; -/** Length of EC point (compressed) */ +/** + * Length of EC point (compressed) + */ constexpr std::size_t kCompressedEcPointLength = 33; -/** Length of one compressed EC point component in an EC ElGamal ciphertext. */ +/** + * Length of one compressed EC point component in an EC ElGamal ciphertext. + */ constexpr std::size_t kEcCiphertextComponentLength = kMPT_ELGAMAL_CIPHER_SIZE; -/** EC ElGamal ciphertext length: two compressed EC points concatenated. */ +/** + * EC ElGamal ciphertext length: two compressed EC points concatenated. + */ constexpr std::size_t kEcGamalEncryptedTotalLength = kMPT_ELGAMAL_TOTAL_SIZE; -/** Length of EC public key (compressed) */ +/** + * Length of EC public key (compressed) + */ constexpr std::size_t kEcPubKeyLength = kMPT_PUBKEY_SIZE; -/** Length of EC private key in bytes */ +/** + * Length of EC private key in bytes + */ constexpr std::size_t kEcPrivKeyLength = kMPT_PRIVKEY_SIZE; -/** Length of the EC blinding factor in bytes */ +/** + * Length of the EC blinding factor in bytes + */ constexpr std::size_t kEcBlindingFactorLength = kMPT_BLINDING_FACTOR_SIZE; -/** Length of Schnorr ZKProof for public key registration (compact form) in bytes */ +/** + * Length of Schnorr ZKProof for public key registration (compact form) in bytes + */ constexpr std::size_t kEcSchnorrProofLength = kMPT_SCHNORR_PROOF_SIZE; -/** Length of Pedersen Commitment (compressed) */ +/** + * Length of Pedersen Commitment (compressed) + */ constexpr std::size_t kEcPedersenCommitmentLength = kMPT_PEDERSEN_COMMIT_SIZE; -/** Length of single bulletproof (range proof for 1 commitment) in bytes */ +/** + * Length of single bulletproof (range proof for 1 commitment) in bytes + */ constexpr std::size_t kEcSingleBulletproofLength = kMPT_SINGLE_BULLETPROOF_SIZE; -/** Length of double bulletproof (range proof for 2 commitments) in bytes */ +/** + * Length of double bulletproof (range proof for 2 commitments) in bytes + */ constexpr std::size_t kEcDoubleBulletproofLength = kMPT_DOUBLE_BULLETPROOF_SIZE; -/** Length of the compact sigma proof component for ConfidentialMPTSend. */ +/** + * Length of the compact sigma proof component for ConfidentialMPTSend. + */ constexpr std::size_t kEcSendSigmaProofLength = SECP256K1_COMPACT_STANDARD_PROOF_SIZE; -/** 192 bytes compact sigma proof + 754 bytes double bulletproof. */ +/** + * 192 bytes compact sigma proof + 754 bytes double bulletproof. + */ constexpr std::size_t kEcSendProofLength = kEcSendSigmaProofLength + kEcDoubleBulletproofLength; -/** Length of the compact sigma proof component for ConfidentialMPTConvertBack. */ +/** + * Length of the compact sigma proof component for ConfidentialMPTConvertBack. + */ constexpr std::size_t kEcConvertBackSigmaProofLength = SECP256K1_COMPACT_CONVERTBACK_PROOF_SIZE; -/** 128 bytes compact sigma proof + 688 bytes single bulletproof. */ +/** + * 128 bytes compact sigma proof + 688 bytes single bulletproof. + */ constexpr std::size_t kEcConvertBackProofLength = kEcConvertBackSigmaProofLength + kEcSingleBulletproofLength; -/** Length of the ZKProof for ConfidentialMPTClawback. */ +/** + * Length of the ZKProof for ConfidentialMPTClawback. + */ constexpr std::size_t kEcClawbackProofLength = SECP256K1_COMPACT_CLAWBACK_PROOF_SIZE; -/** Extra base fee multiplier charged to confidential MPT transactions. */ +/** + * Extra base fee multiplier charged to confidential MPT transactions. + */ constexpr std::uint32_t kConfidentialFeeMultiplier = 9; -/** Compressed EC point prefix for even y-coordinate */ +/** + * Compressed EC point prefix for even y-coordinate + */ constexpr std::uint8_t kEcCompressedPrefixEvenY = 0x02; -/** Compressed EC point prefix for odd y-coordinate */ +/** + * Compressed EC point prefix for odd y-coordinate + */ constexpr std::uint8_t kEcCompressedPrefixOddY = 0x03; } // namespace xrpl diff --git a/include/xrpl/protocol/PublicKey.h b/include/xrpl/protocol/PublicKey.h index 13db17fc6e..98301af487 100644 --- a/include/xrpl/protocol/PublicKey.h +++ b/include/xrpl/protocol/PublicKey.h @@ -26,28 +26,29 @@ namespace xrpl { -/** A public key. - - Public keys are used in the public-key cryptography - system used to verify signatures attached to messages. - - The format of the public key is XRPL specific, - information needed to determine the cryptosystem - parameters used is stored inside the key. - - As of this writing two systems are supported: - - secp256k1 - ed25519 - - secp256k1 public keys consist of a 33 byte - compressed public key, with the lead byte equal - to 0x02 or 0x03. - - The ed25519 public keys consist of a 1 byte - prefix constant 0xED, followed by 32 bytes of - public key data. -*/ +/** + * A public key. + * + * Public keys are used in the public-key cryptography + * system used to verify signatures attached to messages. + * + * The format of the public key is XRPL specific, + * information needed to determine the cryptosystem + * parameters used is stored inside the key. + * + * As of this writing two systems are supported: + * + * secp256k1 + * ed25519 + * + * secp256k1 public keys consist of a 33 byte + * compressed public key, with the lead byte equal + * to 0x02 or 0x03. + * + * The ed25519 public keys consist of a 1 byte + * prefix constant 0xED, followed by 32 bytes of + * public key data. + */ class PublicKey { protected: @@ -66,11 +67,12 @@ public: PublicKey& operator=(PublicKey const& other); - /** Create a public key. - - Preconditions: - publicKeyType(slice) != std::nullopt - */ + /** + * Create a public key. + * + * Preconditions: + * publicKeyType(slice) != std::nullopt + */ explicit PublicKey(Slice const& slice); [[nodiscard]] std::uint8_t const* @@ -121,7 +123,8 @@ public: } }; -/** Print the public key to a stream. +/** + * Print the public key to a stream. */ std::ostream& operator<<(std::ostream& os, PublicKey const& pk); @@ -180,39 +183,41 @@ parseBase58(TokenType type, std::string const& s); enum class ECDSACanonicality { Canonical, FullyCanonical }; -/** Determines the canonicality of a signature. - - A canonical signature is in its most reduced form. - For example the R and S components do not contain - additional leading zeroes. However, even in - canonical form, (R,S) and (R,G-S) are both - valid signatures for message M. - - Therefore, to prevent malleability attacks we - define a fully canonical signature as one where: - - R < G - S - - where G is the curve order. - - This routine returns std::nullopt if the format - of the signature is invalid (for example, the - points are encoded incorrectly). - - @return std::nullopt if the signature fails - validity checks. - - @note Only the format of the signature is checked, - no verification cryptography is performed. -*/ +/** + * Determines the canonicality of a signature. + * + * A canonical signature is in its most reduced form. + * For example the R and S components do not contain + * additional leading zeroes. However, even in + * canonical form, (R,S) and (R,G-S) are both + * valid signatures for message M. + * + * Therefore, to prevent malleability attacks we + * define a fully canonical signature as one where: + * + * R < G - S + * + * where G is the curve order. + * + * This routine returns std::nullopt if the format + * of the signature is invalid (for example, the + * points are encoded incorrectly). + * + * @return std::nullopt if the signature fails + * validity checks. + * + * @note Only the format of the signature is checked, + * no verification cryptography is performed. + */ std::optional ecdsaCanonicality(Slice const& sig); -/** Returns the type of public key. - - @return std::nullopt If the public key does not - represent a known type. -*/ +/** + * Returns the type of public key. + * + * @return std::nullopt If the public key does not + * represent a known type. + */ /** @{ */ [[nodiscard]] std::optional publicKeyType(Slice const& slice); @@ -224,7 +229,9 @@ publicKeyType(PublicKey const& publicKey) } /** @} */ -/** Verify a secp256k1 signature on the digest of a message. */ +/** + * Verify a secp256k1 signature on the digest of a message. + */ [[nodiscard]] bool verifyDigest( PublicKey const& publicKey, @@ -232,14 +239,17 @@ verifyDigest( Slice const& sig, bool mustBeFullyCanonical = true) noexcept; -/** Verify a signature on a message. - With secp256k1 signatures, the data is first hashed with - SHA512-Half, and the resulting digest is signed. -*/ +/** + * Verify a signature on a message. + * With secp256k1 signatures, the data is first hashed with + * SHA512-Half, and the resulting digest is signed. + */ [[nodiscard]] bool verify(PublicKey const& publicKey, Slice const& m, Slice const& sig) noexcept; -/** Calculate the 160-bit node ID from a node public key. */ +/** + * Calculate the 160-bit node ID from a node public key. + */ NodeID calcNodeID(PublicKey const&); diff --git a/include/xrpl/protocol/Quality.h b/include/xrpl/protocol/Quality.h index de61d79ca5..3475efa977 100644 --- a/include/xrpl/protocol/Quality.h +++ b/include/xrpl/protocol/Quality.h @@ -14,15 +14,16 @@ namespace xrpl { -/** Represents a pair of input and output currencies. - - The input currency can be converted to the output - currency by multiplying by the rate, represented by - Quality. - - For offers, "in" is always TakerPays and "out" is - always TakerGets. -*/ +/** + * Represents a pair of input and output currencies. + * + * The input currency can be converted to the output + * currency by multiplying by the rate, represented by + * Quality. + * + * For offers, "in" is always TakerPays and "out" is + * always TakerGets. + */ template struct TAmounts { @@ -36,7 +37,9 @@ struct TAmounts { } - /** Returns `true` if either quantity is not positive. */ + /** + * Returns `true` if either quantity is not positive. + */ [[nodiscard]] bool empty() const noexcept { @@ -84,11 +87,12 @@ operator!=(TAmounts const& lhs, TAmounts const& rhs) noexcept // XRPL specific constant used for parsing qualities and other things #define QUALITY_ONE 1'000'000'000 -/** Represents the logical ratio of output currency to input currency. - Internally this is stored using a custom floating point representation, - as the inverse of the ratio, so that quality will be descending in - a sequence of actual values that represent qualities. -*/ +/** + * Represents the logical ratio of output currency to input currency. + * Internally this is stored using a custom floating point representation, + * as the inverse of the ratio, so that quality will be descending in + * a sequence of actual values that represent qualities. + */ class Quality { public: @@ -109,26 +113,36 @@ private: public: Quality() = default; - /** Create a quality from the integer encoding of an STAmount */ + /** + * Create a quality from the integer encoding of an STAmount + */ explicit Quality(std::uint64_t value); - /** Create a quality from the ratio of two amounts. */ + /** + * Create a quality from the ratio of two amounts. + */ explicit Quality(Amounts const& amount); - /** Create a quality from the ratio of two amounts. */ + /** + * Create a quality from the ratio of two amounts. + */ template explicit Quality(TAmounts const& amount) : Quality(Amounts(toSTAmount(amount.in), toSTAmount(amount.out))) { } - /** Create a quality from the ratio of two amounts. */ + /** + * Create a quality from the ratio of two amounts. + */ template Quality(Out const& out, In const& in) : Quality(Amounts(toSTAmount(in), toSTAmount(out))) { } - /** Advances to the next higher quality level. */ + /** + * Advances to the next higher quality level. + */ /** @{ */ Quality& operator++(); @@ -137,7 +151,9 @@ public: operator++(int); /** @} */ - /** Advances to the next lower quality level. */ + /** + * Advances to the next lower quality level. + */ /** @{ */ Quality& operator--(); @@ -146,23 +162,27 @@ public: operator--(int); /** @} */ - /** Returns the quality as STAmount. */ + /** + * Returns the quality as STAmount. + */ [[nodiscard]] STAmount rate() const { return amountFromQuality(value_); } - /** Returns the quality rounded up to the specified number - of decimal digits. - */ + /** + * Returns the quality rounded up to the specified number + * of decimal digits. + */ [[nodiscard]] Quality round(int tickSize) const; - /** Returns the scaled amount with in capped. - Math is avoided if the result is exact. The output is clamped - to prevent money creation. - */ + /** + * Returns the scaled amount with in capped. + * Math is avoided if the result is exact. The output is clamped + * to prevent money creation. + */ [[nodiscard]] Amounts ceilIn(Amounts const& amount, STAmount const& limit) const; @@ -180,10 +200,11 @@ public: [[nodiscard]] TAmounts ceilInStrict(TAmounts const& amount, In const& limit, bool roundUp) const; - /** Returns the scaled amount with out capped. - Math is avoided if the result is exact. The input is clamped - to prevent money creation. - */ + /** + * Returns the scaled amount with out capped. + * Math is avoided if the result is exact. The input is clamped + * to prevent money creation. + */ [[nodiscard]] Amounts ceilOut(Amounts const& amount, STAmount const& limit) const; @@ -215,10 +236,11 @@ private: Round... round) const; public: - /** Returns `true` if lhs is lower quality than `rhs`. - Lower quality means the taker receives a worse deal. - Higher quality is better for the taker. - */ + /** + * Returns `true` if lhs is lower quality than `rhs`. + * Lower quality means the taker receives a worse deal. + * Higher quality is better for the taker. + */ friend bool operator<(Quality const& lhs, Quality const& rhs) noexcept { @@ -357,10 +379,11 @@ Quality::ceilOutStrict(TAmounts const& amount, Out const& limit, bool r return ceilTAmountsHelper(amount, limit, amount.out, kCeilOutFnPtr, roundUp); } -/** Calculate the quality of a two-hop path given the two hops. - @param lhs The first leg of the path: input to intermediate. - @param rhs The second leg of the path: intermediate to output. -*/ +/** + * Calculate the quality of a two-hop path given the two hops. + * @param lhs The first leg of the path: input to intermediate. + * @param rhs The second leg of the path: intermediate to output. + */ Quality composedQuality(Quality const& lhs, Quality const& rhs); diff --git a/include/xrpl/protocol/QualityFunction.h b/include/xrpl/protocol/QualityFunction.h index 7830519deb..128b37ce12 100644 --- a/include/xrpl/protocol/QualityFunction.h +++ b/include/xrpl/protocol/QualityFunction.h @@ -12,7 +12,8 @@ namespace xrpl { -/** Average quality of a path as a function of `out`: q(out) = m * out + b, +/** + * Average quality of a path as a function of `out`: q(out) = m * out + b, * where m = -1 / poolGets, b = poolPays / poolGets. If CLOB offer then * `m` is equal to 0 `b` is equal to the offer's quality. The function * is derived by substituting `in` in q = out / in with the swap out formula @@ -45,19 +46,22 @@ public: template QualityFunction(TAmounts const& amounts, std::uint32_t tfee, AMMTag); - /** Combines QF with the next step QF + /** + * Combines QF with the next step QF */ void combine(QualityFunction const& qf); - /** Find output to produce the requested + /** + * Find output to produce the requested * average quality. * @param quality requested average quality (quality limit) */ std::optional outFromAvgQ(Quality const& quality); - /** Return true if the quality function is constant + /** + * Return true if the quality function is constant */ [[nodiscard]] bool isConst() const diff --git a/include/xrpl/protocol/Rate.h b/include/xrpl/protocol/Rate.h index b8b04c8fb9..048787cab5 100644 --- a/include/xrpl/protocol/Rate.h +++ b/include/xrpl/protocol/Rate.h @@ -10,12 +10,13 @@ namespace xrpl { -/** Represents a transfer rate - - Transfer rates are specified as fractions of 1 billion. - For example, a transfer rate of 1% is represented as - 1,010,000,000. -*/ +/** + * Represents a transfer rate + * + * Transfer rates are specified as fractions of 1 billion. + * For example, a transfer rate of 1% is represented as + * 1,010,000,000. + */ struct Rate : private boost::totally_ordered { std::uint32_t value; @@ -65,13 +66,17 @@ STAmount divideRound(STAmount const& amount, Rate const& rate, Asset const& asset, bool roundUp); namespace nft { -/** Given a transfer fee (in basis points) convert it to a transfer rate. */ +/** + * Given a transfer fee (in basis points) convert it to a transfer rate. + */ Rate transferFeeAsRate(std::uint16_t fee); } // namespace nft -/** A transfer rate signifying a 1:1 exchange */ +/** + * A transfer rate signifying a 1:1 exchange + */ extern Rate const kParityRate; } // namespace xrpl diff --git a/include/xrpl/protocol/Rules.h b/include/xrpl/protocol/Rules.h index da2031650f..2c2136b6e8 100644 --- a/include/xrpl/protocol/Rules.h +++ b/include/xrpl/protocol/Rules.h @@ -11,7 +11,8 @@ namespace xrpl { -/** Check whether a feature is enabled in the current ledger rules +/** + * Check whether a feature is enabled in the current ledger rules * * @param feature The feature to be tested. * @param resultIfNoRules What to return if called from outside a Transactor context. @@ -19,7 +20,8 @@ namespace xrpl { bool isFeatureEnabled(uint256 const& feature, bool resultIfNoRules); -/** Check whether a feature is enabled in the current ledger rules +/** + * Check whether a feature is enabled in the current ledger rules * * @param feature The feature to be tested. * @@ -31,7 +33,9 @@ isFeatureEnabled(uint256 const& feature); class DigestAwareReadView; -/** Rules controlling protocol behavior. */ +/** + * Rules controlling protocol behavior. + */ class Rules { private: @@ -54,11 +58,12 @@ public: Rules() = delete; - /** Construct an empty rule set. - - These are the rules reflected by - the genesis ledger. - */ + /** + * Construct an empty rule set. + * + * These are the rules reflected by + * the genesis ledger. + */ explicit Rules(std::unordered_set> const& presets); private: @@ -80,14 +85,17 @@ private: presets() const; public: - /** Returns `true` if a feature is enabled. */ + /** + * Returns `true` if a feature is enabled. + */ [[nodiscard]] bool enabled(uint256 const& feature) const; - /** Returns `true` if two rule sets are identical. - - @note This is for diagnostics. - */ + /** + * Returns `true` if two rule sets are identical. + * + * @note This is for diagnostics. + */ bool operator==(Rules const&) const; @@ -101,7 +109,8 @@ getCurrentTransactionRules(); void setCurrentTransactionRules(std::optional r); -/** RAII class to set and restore the current transaction rules +/** + * RAII class to set and restore the current transaction rules */ class CurrentTransactionRulesGuard { diff --git a/include/xrpl/protocol/SField.h b/include/xrpl/protocol/SField.h index d97bcb0a1d..21ab7813f9 100644 --- a/include/xrpl/protocol/SField.h +++ b/include/xrpl/protocol/SField.h @@ -117,16 +117,17 @@ fieldCode(int id, int index) return (id << 16) | index; } -/** Identifies fields. - - Fields are necessary to tag data in signed transactions so that - the binary format of the transaction can be canonicalized. All - SFields are created at compile time. - - Each SField, once constructed, lives until program termination, and there - is only one instance per fieldType/fieldValue pair which serves the - entire application. -*/ +/** + * Identifies fields. + * + * Fields are necessary to tag data in signed transactions so that + * the binary format of the transaction can be canonicalized. All + * SFields are created at compile time. + * + * Each SField, once constructed, lives until program termination, and there + * is only one instance per fieldType/fieldValue pair which serves the + * entire application. + */ class SField { public: @@ -299,7 +300,9 @@ private: static std::unordered_map knownNameToField; }; -/** A field with a type known at compile time. */ +/** + * A field with a type known at compile time. + */ template struct TypedField : SField { @@ -309,7 +312,9 @@ struct TypedField : SField explicit TypedField(PrivateAccessTagT pat, Args&&... args); }; -/** Indicate std::optional field semantics. */ +/** + * Indicate std::optional field semantics. + */ template struct OptionaledField { diff --git a/include/xrpl/protocol/SOTemplate.h b/include/xrpl/protocol/SOTemplate.h index 682a7c655e..cb24ee315a 100644 --- a/include/xrpl/protocol/SOTemplate.h +++ b/include/xrpl/protocol/SOTemplate.h @@ -12,7 +12,9 @@ namespace xrpl { -/** Kind of element in each entry of an SOTemplate. */ +/** + * Kind of element in each entry of an SOTemplate. + */ // 2026 usages, 129 files // NOLINTNEXTLINE(cppcoreguidelines-use-enum-class) enum SOEStyle { @@ -25,13 +27,17 @@ enum SOEStyle { }; // Part of a Python-parsed DSL (transactions.macro); bare enumerator names required by the parser -/** Amount fields that can support MPT */ +/** + * Amount fields that can support MPT + */ // NOLINTNEXTLINE(cppcoreguidelines-use-enum-class) enum SOETxMPTIssue { SoeMptNone, SoeMptSupported, SoeMptNotSupported }; //------------------------------------------------------------------------------ -/** An element in a SOTemplate. */ +/** + * An element in a SOTemplate. + */ class SOElement { // Use std::reference_wrapper so SOElement can be stored in a std::vector. @@ -90,10 +96,11 @@ public: //------------------------------------------------------------------------------ -/** Defines the fields and their attributes within a STObject. - Each subclass of SerializedObject will provide its own template - describing the available fields and their metadata attributes. -*/ +/** + * Defines the fields and their attributes within a STObject. + * Each subclass of SerializedObject will provide its own template + * describing the available fields and their metadata attributes. + */ class SOTemplate { public: @@ -103,14 +110,16 @@ public: SOTemplate& operator=(SOTemplate&& other) = default; - /** Create a template populated with all fields. - After creating the template fields cannot be added, modified, or removed. - */ + /** + * Create a template populated with all fields. + * After creating the template fields cannot be added, modified, or removed. + */ SOTemplate(std::vector uniqueFields, std::vector commonFields = {}); - /** Create a template populated with all fields. - Note: Defers to the vector constructor above. - */ + /** + * Create a template populated with all fields. + * Note: Defers to the vector constructor above. + */ SOTemplate( std::initializer_list uniqueFields, std::initializer_list commonFields = {}); @@ -140,14 +149,18 @@ public: return end(); } - /** The number of entries in this template */ + /** + * The number of entries in this template + */ [[nodiscard]] std::size_t size() const { return elements_.size(); } - /** Retrieve the position of a named field. */ + /** + * Retrieve the position of a named field. + */ [[nodiscard]] int getIndex(SField const&) const; diff --git a/include/xrpl/protocol/STAmount.h b/include/xrpl/protocol/STAmount.h index 5e53a85129..cc80481582 100644 --- a/include/xrpl/protocol/STAmount.h +++ b/include/xrpl/protocol/STAmount.h @@ -190,7 +190,9 @@ public: [[nodiscard]] int signum() const noexcept; - /** Returns a zero value with the same issuer and currency. */ + /** + * Returns a zero value with the same issuer and currency. + */ [[nodiscard]] STAmount zeroed() const; @@ -255,7 +257,9 @@ public: void clear(Asset const& asset); - /** Set the Issue for this amount. */ + /** + * Set the Issue for this amount. + */ void setIssue(Asset const& asset); @@ -704,7 +708,8 @@ divRoundStrict(STAmount const& v1, STAmount const& v2, Asset const& asset, bool std::uint64_t getRate(STAmount const& offerOut, STAmount const& offerIn); -/** Round an arbitrary precision Amount to the precision of an STAmount that has +/** + * Round an arbitrary precision Amount to the precision of an STAmount that has * a given exponent. * * This is used to ensure that calculations involving IOU amounts do not collect @@ -714,7 +719,6 @@ getRate(STAmount const& offerOut, STAmount const& offerIn); * @param scale An exponent value to establish the precision limit of * `value`. Should be larger than `value.exponent()`. * @param rounding Optional Number rounding mode - * */ [[nodiscard]] STAmount roundToScale( @@ -722,7 +726,8 @@ roundToScale( std::int32_t scale, Number::RoundingMode rounding = Number::getround()); -/** Round an arbitrary precision Number IN PLACE to the precision of a given +/** + * Round an arbitrary precision Number IN PLACE to the precision of a given * Asset. * * This is used to ensure that calculations do not collect dust for IOUs, or @@ -738,7 +743,8 @@ roundToAsset(A const& asset, Number& value) value = STAmount{asset, value}; } -/** Round an arbitrary precision Number to the precision of a given Asset. +/** + * Round an arbitrary precision Number to the precision of a given Asset. * * This is used to ensure that calculations do not collect dust beyond specified * scale for IOUs, or fractional amounts for the integral types XRP and MPT. @@ -780,7 +786,8 @@ canAdd(STAmount const& amt1, STAmount const& amt2); bool canSubtract(STAmount const& amt1, STAmount const& amt2); -/** Get the scale of a Number for a given asset. +/** + * Get the scale of a Number for a given asset. * * "scale" is similar to "exponent", but from the perspective of STAmount, which has different rules * and mantissa ranges for determining the exponent than Number. diff --git a/include/xrpl/protocol/STBase.h b/include/xrpl/protocol/STBase.h index 341c80edd7..acc5500a57 100644 --- a/include/xrpl/protocol/STBase.h +++ b/include/xrpl/protocol/STBase.h @@ -15,7 +15,9 @@ namespace xrpl { -/// Note, should be treated as flags that can be | and & +/** + * Note, should be treated as flags that can be | and & + */ struct JsonOptions { using underlying_t = unsigned int; @@ -53,22 +55,28 @@ struct JsonOptions [[nodiscard]] constexpr auto friend operator!=(JsonOptions lh, JsonOptions rh) noexcept -> bool = default; - /// Returns JsonOptions union of lh and rh + /** + * Returns JsonOptions union of lh and rh + */ [[nodiscard]] constexpr JsonOptions friend operator|(JsonOptions lh, JsonOptions rh) noexcept { return {lh.value | rh.value}; } - /// Returns JsonOptions intersection of lh and rh + /** + * Returns JsonOptions intersection of lh and rh + */ [[nodiscard]] constexpr JsonOptions friend operator&(JsonOptions lh, JsonOptions rh) noexcept { return {lh.value & rh.value}; } - /// Returns JsonOptions binary negation, can be used with & (above) for set - /// difference e.g. `(options & ~JsonOptions::kIncludeDate)` + /** + * Returns JsonOptions binary negation, can be used with & (above) for set + * difference e.g. `(options & ~JsonOptions::kIncludeDate)` + */ [[nodiscard]] constexpr JsonOptions friend operator~(JsonOptions v) noexcept { @@ -103,19 +111,20 @@ class STVar; //------------------------------------------------------------------------------ -/** A type which can be exported to a well known binary format. - - A STBase: - - Always a field - - Can always go inside an eligible enclosing STBase - (such as STArray) - - Has a field name - - Like JSON, a SerializedObject is a basket which has rules - on what it can hold. - - @note "ST" stands for "Serialized Type." -*/ +/** + * A type which can be exported to a well known binary format. + * + * A STBase: + * - Always a field + * - Can always go inside an eligible enclosing STBase + * (such as STArray) + * - Has a field name + * + * Like JSON, a SerializedObject is a basket which has rules + * on what it can hold. + * + * @note "ST" stands for "Serialized Type." + */ class STBase { SField const* fName_; @@ -162,9 +171,10 @@ public: [[nodiscard]] virtual bool isDefault() const; - /** A STBase is a field. - This sets the name. - */ + /** + * A STBase is a field. + * This sets the name. + */ void setFName(SField const& n); diff --git a/include/xrpl/protocol/STExchange.h b/include/xrpl/protocol/STExchange.h index a9c1f57bd8..ad5bd4c012 100644 --- a/include/xrpl/protocol/STExchange.h +++ b/include/xrpl/protocol/STExchange.h @@ -18,7 +18,9 @@ namespace xrpl { -/** Convert between serialized type U and C++ type T. */ +/** + * Convert between serialized type U and C++ type T. + */ template struct STExchange; @@ -90,7 +92,9 @@ struct STExchange //------------------------------------------------------------------------------ -/** Return the value of a field in an STObject as a given type. */ +/** + * Return the value of a field in an STObject as a given type. + */ /** @{ */ template std::optional @@ -119,7 +123,9 @@ get(STObject const& st, TypedField const& f) } /** @} */ -/** Set a field value in an STObject. */ +/** + * Set a field value in an STObject. + */ template void set(STObject& st, TypedField const& f, T&& t) @@ -127,7 +133,9 @@ set(STObject& st, TypedField const& f, T&& t) st.set(STExchange>::set(f, std::forward(t))); } -/** Set a blob field using an init function. */ +/** + * Set a blob field using an init function. + */ template void set(STObject& st, TypedField const& f, std::size_t size, Init&& init) @@ -135,7 +143,9 @@ set(STObject& st, TypedField const& f, std::size_t size, Init&& init) st.set(std::make_unique(f, size, init)); } -/** Set a blob field from data. */ +/** + * Set a blob field from data. + */ template void set(STObject& st, TypedField const& f, void const* data, std::size_t size) @@ -143,7 +153,9 @@ set(STObject& st, TypedField const& f, void const* data, std::size_t siz st.set(std::make_unique(f, data, size)); } -/** Remove a field in an STObject. */ +/** + * Remove a field in an STObject. + */ template void erase(STObject& st, TypedField const& f) diff --git a/include/xrpl/protocol/STLedgerEntry.h b/include/xrpl/protocol/STLedgerEntry.h index a5f449f99c..8731488adb 100644 --- a/include/xrpl/protocol/STLedgerEntry.h +++ b/include/xrpl/protocol/STLedgerEntry.h @@ -33,7 +33,9 @@ public: using const_pointer = std::shared_ptr; using const_ref = std::shared_ptr const&; - /** Create an empty object with the given key and type. */ + /** + * Create an empty object with the given key and type. + */ explicit STLedgerEntry(Keylet const& k); STLedgerEntry(LedgerEntryType type, uint256 const& key); STLedgerEntry(SerialIter& sit, uint256 const& index); @@ -52,10 +54,11 @@ public: [[nodiscard]] json::Value getJson(JsonOptions options = JsonOptions::Values::None) const override; - /** Returns the 'key' (or 'index') of this item. - The key identifies this entry's position in - the SHAMap associative container. - */ + /** + * Returns the 'key' (or 'index') of this item. + * The key identifies this entry's position in + * the SHAMap associative container. + */ [[nodiscard]] uint256 const& key() const; @@ -105,10 +108,11 @@ inline STLedgerEntry::STLedgerEntry( { } -/** Returns the 'key' (or 'index') of this item. - The key identifies this entry's position in - the SHAMap associative container. -*/ +/** + * Returns the 'key' (or 'index') of this item. + * The key identifies this entry's position in + * the SHAMap associative container. + */ inline uint256 const& STLedgerEntry::key() const { diff --git a/include/xrpl/protocol/STObject.h b/include/xrpl/protocol/STObject.h index fe5c611648..a1cdff22e0 100644 --- a/include/xrpl/protocol/STObject.h +++ b/include/xrpl/protocol/STObject.h @@ -229,10 +229,12 @@ public: [[nodiscard]] AccountID getAccountID(SField const& field) const; - /** The account responsible for the fee and authorization: the delegate when - sfDelegate is present, otherwise the account. */ + /** + * The account responsible for the authorization: the delegate when + * sfDelegate is present, otherwise the account. + */ [[nodiscard]] AccountID - getFeePayer() const; + getInitiator() const; [[nodiscard]] Blob getFieldVL(SField const& field) const; @@ -252,103 +254,112 @@ public: [[nodiscard]] STNumber const& getFieldNumber(SField const& field) const; - /** Get the value of a field. - @param A TypedField built from an SField value representing the desired - object field. In typical use, the TypedField will be implicitly - constructed. - @return The value of the specified field. - @throws STObject::FieldErr if the field is not present. - */ + /** + * Get the value of a field. + * @param A TypedField built from an SField value representing the desired + * object field. In typical use, the TypedField will be implicitly + * constructed. + * @return The value of the specified field. + * @throws STObject::FieldErr if the field is not present. + */ template T::value_type operator[](TypedField const& f) const; - /** Get the value of a field as a std::optional - - @param An OptionaledField built from an SField value representing the - desired object field. In typical use, the OptionaledField will be - constructed by using the ~ operator on an SField. - @return std::nullopt if the field is not present, else the value of - the specified field. - */ + /** + * Get the value of a field as a std::optional + * + * @param An OptionaledField built from an SField value representing the + * desired object field. In typical use, the OptionaledField will be + * constructed by using the ~ operator on an SField. + * @return std::nullopt if the field is not present, else the value of + * the specified field. + */ template std::optional> operator[](OptionaledField const& of) const; - /** Get a modifiable field value. - @param A TypedField built from an SField value representing the desired - object field. In typical use, the TypedField will be implicitly - constructed. - @return A modifiable reference to the value of the specified field. - @throws STObject::FieldErr if the field is not present. - */ + /** + * Get a modifiable field value. + * @param A TypedField built from an SField value representing the desired + * object field. In typical use, the TypedField will be implicitly + * constructed. + * @return A modifiable reference to the value of the specified field. + * @throws STObject::FieldErr if the field is not present. + */ template ValueProxy operator[](TypedField const& f); - /** Return a modifiable field value as std::optional - - @param An OptionaledField built from an SField value representing the - desired object field. In typical use, the OptionaledField will be - constructed by using the ~ operator on an SField. - @return Transparent proxy object to an `optional` holding a modifiable - reference to the value of the specified field. Returns - std::nullopt if the field is not present. - */ + /** + * Return a modifiable field value as std::optional + * + * @param An OptionaledField built from an SField value representing the + * desired object field. In typical use, the OptionaledField will be + * constructed by using the ~ operator on an SField. + * @return Transparent proxy object to an `optional` holding a modifiable + * reference to the value of the specified field. Returns + * std::nullopt if the field is not present. + */ template OptionalProxy operator[](OptionaledField const& of); - /** Get the value of a field. - @param A TypedField built from an SField value representing the desired - object field. In typical use, the TypedField will be implicitly - constructed. - @return The value of the specified field. - @throws STObject::FieldErr if the field is not present. - */ + /** + * Get the value of a field. + * @param A TypedField built from an SField value representing the desired + * object field. In typical use, the TypedField will be implicitly + * constructed. + * @return The value of the specified field. + * @throws STObject::FieldErr if the field is not present. + */ template [[nodiscard]] T::value_type at(TypedField const& f) const; - /** Get the value of a field as std::optional - - @param An OptionaledField built from an SField value representing the - desired object field. In typical use, the OptionaledField will be - constructed by using the ~ operator on an SField. - @return std::nullopt if the field is not present, else the value of - the specified field. - */ + /** + * Get the value of a field as std::optional + * + * @param An OptionaledField built from an SField value representing the + * desired object field. In typical use, the OptionaledField will be + * constructed by using the ~ operator on an SField. + * @return std::nullopt if the field is not present, else the value of + * the specified field. + */ template [[nodiscard]] std::optional> at(OptionaledField const& of) const; - /** Get a modifiable field value. - @param A TypedField built from an SField value representing the desired - object field. In typical use, the TypedField will be implicitly - constructed. - @return A modifiable reference to the value of the specified field. - @throws STObject::FieldErr if the field is not present. - */ + /** + * Get a modifiable field value. + * @param A TypedField built from an SField value representing the desired + * object field. In typical use, the TypedField will be implicitly + * constructed. + * @return A modifiable reference to the value of the specified field. + * @throws STObject::FieldErr if the field is not present. + */ template ValueProxy at(TypedField const& f); - /** Return a modifiable field value as std::optional - - @param An OptionaledField built from an SField value representing the - desired object field. In typical use, the OptionaledField will be - constructed by using the ~ operator on an SField. - @return Transparent proxy object to an `optional` holding a modifiable - reference to the value of the specified field. Returns - std::nullopt if the field is not present. - */ + /** + * Return a modifiable field value as std::optional + * + * @param An OptionaledField built from an SField value representing the + * desired object field. In typical use, the OptionaledField will be + * constructed by using the ~ operator on an SField. + * @return Transparent proxy object to an `optional` holding a modifiable + * reference to the value of the specified field. Returns + * std::nullopt if the field is not present. + */ template OptionalProxy at(OptionaledField const& of); - /** Set a field. - if the field already exists, it is replaced. - */ + /** + * Set a field. + * if the field already exists, it is replaced. + */ void set(std::unique_ptr v); @@ -503,8 +514,10 @@ public: value_type operator*() const; - /// Do not use operator->() unless the field is required, or you've checked - /// that it's set. + /** + * Do not use operator->() unless the field is required, or you've checked + * that it's set. + */ T const* operator->() const; @@ -604,17 +617,20 @@ public: OptionalProxy& operator=(OptionalProxy const&) = delete; - /** Returns `true` if the field is set. - - Fields with soeDEFAULT and set to the - default value will return `true` - */ + /** + * Returns `true` if the field is set. + * + * Fields with soeDEFAULT and set to the + * default value will return `true` + */ explicit operator bool() const noexcept; operator optional_type() const; - /** Explicit conversion to std::optional */ + /** + * Explicit conversion to std::optional + */ optional_type operator~() const; @@ -761,8 +777,10 @@ STObject::Proxy::operator*() const -> value_type return this->value(); } -/// Do not use operator->() unless the field is required, or you've checked that -/// it's set. +/** + * Do not use operator->() unless the field is required, or you've checked that + * it's set. + */ template T const* STObject::Proxy::operator->() const diff --git a/include/xrpl/protocol/STParsedJSON.h b/include/xrpl/protocol/STParsedJSON.h index 1eeecc8b9e..7189e0ec89 100644 --- a/include/xrpl/protocol/STParsedJSON.h +++ b/include/xrpl/protocol/STParsedJSON.h @@ -9,26 +9,33 @@ namespace xrpl { -/** Maximum JSON object nesting depth permitted during parsing. */ +/** + * Maximum JSON object nesting depth permitted during parsing. + */ inline constexpr std::size_t kMaxParsedJsonDepth = 64; -/** Maximum number of elements permitted in any JSON array field during parsing. - Requests exceeding this limit are rejected with an invalidParams error. */ +/** + * Maximum number of elements permitted in any JSON array field during parsing. + * Requests exceeding this limit are rejected with an invalidParams error. + */ inline constexpr std::size_t kMaxParsedJsonArraySize = 512; -/** Holds the serialized result of parsing an input JSON object. - This does validation and checking on the provided JSON. -*/ +/** + * Holds the serialized result of parsing an input JSON object. + * This does validation and checking on the provided JSON. + */ class STParsedJSONObject { public: - /** Parses and creates an STParsedJSON object. - The result of the parsing is stored in object and error. - Exceptions: - Does not throw. - @param name The name of the JSON field, used in diagnostics. - @param json The JSON-RPC to parse. - */ + /** + * Parses and creates an STParsedJSON object. + * The result of the parsing is stored in object and error. + * + * @note Does not throw. + * + * @param name The name of the JSON field, used in diagnostics. + * @param json The JSON-RPC to parse. + */ STParsedJSONObject(std::string const& name, json::Value const& json); STParsedJSONObject() = delete; @@ -37,10 +44,14 @@ public: operator=(STParsedJSONObject const&) = delete; ~STParsedJSONObject() = default; - /** The STObject if the parse was successful. */ + /** + * The STObject if the parse was successful. + */ std::optional object; - /** On failure, an appropriate set of error values. */ + /** + * On failure, an appropriate set of error values. + */ json::Value error; }; diff --git a/include/xrpl/protocol/STPathSet.h b/include/xrpl/protocol/STPathSet.h index f6b0fde7da..23f4e653c4 100644 --- a/include/xrpl/protocol/STPathSet.h +++ b/include/xrpl/protocol/STPathSet.h @@ -240,6 +240,9 @@ private: inline STPathElement::STPathElement() : type_(TypeNone), isOffer_(true) { + // hashValue_ is derived from the whole object, so it is computed in the body + // once every other member is initialized (as in the other constructors). + // NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer) hashValue_ = getHash(*this); } @@ -315,6 +318,9 @@ inline STPathElement::STPathElement( assetID_.visit( [&](Currency const&) { type_ = type_ & (~Type::TypeMpt); }, [&](MPTID const&) { type_ = type_ & (~Type::TypeCurrency); }); + // hashValue_ must be computed after type_ is adjusted above, so this cannot + // be a member initializer. + // NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer) hashValue_ = getHash(*this); } diff --git a/include/xrpl/protocol/STTakesAsset.h b/include/xrpl/protocol/STTakesAsset.h index 70bafd0e91..95667e4868 100644 --- a/include/xrpl/protocol/STTakesAsset.h +++ b/include/xrpl/protocol/STTakesAsset.h @@ -7,7 +7,8 @@ namespace xrpl { -/** Intermediate class for any STBase-derived class to store an Asset. +/** + * Intermediate class for any STBase-derived class to store an Asset. * * In the class definition, this class should be specified as a base class * _instead_ of STBase. @@ -41,7 +42,8 @@ STTakesAsset::associateAsset(Asset const& a) class STLedgerEntry; -/** Associate an Asset with all sMD_NeedsAsset fields in a ledger entry. +/** + * Associate an Asset with all sMD_NeedsAsset fields in a ledger entry. * * This function iterates over all fields in the given ledger entry. For each * field that is set and has the SField::sMD_NeedsAsset metadata flag, it calls @@ -54,7 +56,6 @@ class STLedgerEntry; * * @param sle The ledger entry whose fields will be updated. * @param asset The Asset to associate with the relevant fields. - * */ void associateAsset(STLedgerEntry& sle, Asset const& asset); diff --git a/include/xrpl/protocol/STTx.h b/include/xrpl/protocol/STTx.h index b36207bf61..989bd11c10 100644 --- a/include/xrpl/protocol/STTx.h +++ b/include/xrpl/protocol/STTx.h @@ -55,12 +55,13 @@ public: explicit STTx(SerialIter&& sit); explicit STTx(STObject&& object); - /** Constructs a transaction. - - The returned transaction will have the specified type and - any fields that the callback function adds to the object - that's passed in. - */ + /** + * Constructs a transaction. + * + * The returned transaction will have the specified type and + * any fields that the callback function adds to the object + * that's passed in. + */ STTx(TxType type, std::function assembler); // STObject functions. @@ -92,7 +93,9 @@ public: [[nodiscard]] SeqProxy getSeqProxy() const; - /** Returns the first non-zero value of (Sequence, TicketSequence). */ + /** + * Returns the first non-zero value of (Sequence, TicketSequence). + */ [[nodiscard]] std::uint32_t getSeqValue() const; @@ -114,10 +117,11 @@ public: SecretKey const& secretKey, std::optional> signatureTarget = {}); - /** Check the signature. - @param rules The current ledger rules. - @return `true` if valid signature. If invalid, the error message string. - */ + /** + * Check the signature. + * @param rules The current ledger rules. + * @return `true` if valid signature. If invalid, the error message string. + */ [[nodiscard]] std::expected checkSign(Rules const& rules) const; @@ -141,13 +145,17 @@ public: [[nodiscard]] std::vector const& getBatchTransactionIDs() const; + [[nodiscard]] AccountID + getFeePayerID() const; + private: - /** Check the signature. - @param rules The current ledger rules. - @param sigObject Reference to object that contains the signature fields. - Will be *this more often than not. - @return `true` if valid signature. If invalid, the error message string. - */ + /** + * Check the signature. + * @param rules The current ledger rules. + * @param sigObject Reference to object that contains the signature fields. + * Will be *this more often than not. + * @return `true` if valid signature. If invalid, the error message string. + */ [[nodiscard]] std::expected checkSign(Rules const& rules, STObject const& sigObject) const; @@ -178,17 +186,20 @@ private: bool passesLocalChecks(STObject const& st, std::string&); -/** Sterilize a transaction. - - The transaction is serialized and then deserialized, - ensuring that all equivalent transactions are in canonical - form. This also ensures that program metadata such as - the transaction's digest, are all computed. -*/ +/** + * Sterilize a transaction. + * + * The transaction is serialized and then deserialized, + * ensuring that all equivalent transactions are in canonical + * form. This also ensures that program metadata such as + * the transaction's digest, are all computed. + */ std::shared_ptr sterilize(STTx const& stx); -/** Check whether a transaction is a pseudo-transaction */ +/** + * Check whether a transaction is a pseudo-transaction + */ bool isPseudoTx(STObject const& tx); diff --git a/include/xrpl/protocol/STValidation.h b/include/xrpl/protocol/STValidation.h index 67a6594419..444fdfa600 100644 --- a/include/xrpl/protocol/STValidation.h +++ b/include/xrpl/protocol/STValidation.h @@ -54,30 +54,32 @@ class STValidation final : public STObject, public CountedObject NetClock::time_point seenTime_; public: - /** Construct a STValidation from a peer from serialized data. - - @param sit Iterator over serialized data - @param lookupNodeID Invocable with signature - NodeID(PublicKey const&) - used to find the Node ID based on the public key - that signed the validation. For manifest based - validators, this should be the NodeID of the master - public key. - @param checkSignature Whether to verify the data was signed properly - - @note Throws if the object is not valid - */ + /** + * Construct a STValidation from a peer from serialized data. + * + * @param sit Iterator over serialized data + * @param lookupNodeID Invocable with signature + * NodeID(PublicKey const&) + * used to find the Node ID based on the public key + * that signed the validation. For manifest based + * validators, this should be the NodeID of the master + * public key. + * @param checkSignature Whether to verify the data was signed properly + * + * @note Throws if the object is not valid + */ template STValidation(SerialIter& sit, LookupNodeID&& lookupNodeID, bool checkSignature); - /** Construct, sign and trust a new STValidation issued by this node. - - @param signTime When the validation is signed - @param publicKey The current signing public key - @param secretKey The current signing secret key - @param nodeID ID corresponding to node's public master key - @param f callback function to "fill" the validation with necessary data - */ + /** + * Construct, sign and trust a new STValidation issued by this node. + * + * @param signTime When the validation is signed + * @param publicKey The current signing public key + * @param secretKey The current signing secret key + * @param nodeID ID corresponding to node's public master key + * @param f callback function to "fill" the validation with necessary data + */ template STValidation( NetClock::time_point signTime, @@ -183,14 +185,15 @@ STValidation::STValidation(SerialIter& sit, LookupNodeID&& lookupNodeID, bool ch XRPL_ASSERT(nodeID_.isNonZero(), "xrpl::STValidation::STValidation(SerialIter) : nonzero node"); } -/** Construct, sign and trust a new STValidation issued by this node. - - @param signTime When the validation is signed - @param publicKey The current signing public key - @param secretKey The current signing secret key - @param nodeID ID corresponding to node's public master key - @param f callback function to "fill" the validation with necessary data -*/ +/** + * Construct, sign and trust a new STValidation issued by this node. + * + * @param signTime When the validation is signed + * @param publicKey The current signing public key + * @param secretKey The current signing secret key + * @param nodeID ID corresponding to node's public master key + * @param f callback function to "fill" the validation with necessary data + */ template STValidation::STValidation( NetClock::time_point signTime, diff --git a/include/xrpl/protocol/STVector256.h b/include/xrpl/protocol/STVector256.h index 46a1abc713..5a8418fef5 100644 --- a/include/xrpl/protocol/STVector256.h +++ b/include/xrpl/protocol/STVector256.h @@ -50,7 +50,9 @@ public: void setValue(STVector256 const& v); - /** Retrieve a copy of the vector we contain */ + /** + * Retrieve a copy of the vector we contain + */ explicit operator std::vector() const; @@ -138,7 +140,9 @@ STVector256::setValue(STVector256 const& v) value_ = v.value_; } -/** Retrieve a copy of the vector we contain */ +/** + * Retrieve a copy of the vector we contain + */ inline STVector256:: operator std::vector() const { diff --git a/include/xrpl/protocol/SecretKey.h b/include/xrpl/protocol/SecretKey.h index 8a0d917ab4..6d353acac0 100644 --- a/include/xrpl/protocol/SecretKey.h +++ b/include/xrpl/protocol/SecretKey.h @@ -17,7 +17,9 @@ namespace xrpl { -/** A secret key. */ +/** + * A secret key. + */ class SecretKey { public: @@ -56,11 +58,12 @@ public: return sizeof(buf_); } - /** Convert the secret key to a hexadecimal string. - - @note The operator<< function is deliberately omitted - to avoid accidental exposure of secret key material. - */ + /** + * Convert the secret key to a hexadecimal string. + * + * @note The operator<< function is deliberately omitted + * to avoid accidental exposure of secret key material. + */ [[nodiscard]] std::string toString() const; @@ -97,7 +100,9 @@ operator!=(SecretKey const& lhs, SecretKey const& rhs) = delete; //------------------------------------------------------------------------------ -/** Parse a secret key */ +/** + * Parse a secret key + */ template <> std::optional parseBase58(TokenType type, std::string const& s); @@ -108,38 +113,48 @@ toBase58(TokenType type, SecretKey const& sk) return encodeBase58Token(type, sk.data(), sk.size()); } -/** Create a secret key using secure random numbers. */ +/** + * Create a secret key using secure random numbers. + */ SecretKey randomSecretKey(); -/** Generate a new secret key deterministically. */ +/** + * Generate a new secret key deterministically. + */ SecretKey generateSecretKey(KeyType type, Seed const& seed); -/** Derive the public key from a secret key. */ +/** + * Derive the public key from a secret key. + */ PublicKey derivePublicKey(KeyType type, SecretKey const& sk); -/** Generate a key pair deterministically. - - This algorithm is specific to the XRPL: - - For secp256k1 key pairs, the seed is converted - to a Generator and used to compute the key pair - corresponding to ordinal 0 for the generator. -*/ +/** + * Generate a key pair deterministically. + * + * This algorithm is specific to the XRPL: + * + * For secp256k1 key pairs, the seed is converted + * to a Generator and used to compute the key pair + * corresponding to ordinal 0 for the generator. + */ std::pair generateKeyPair(KeyType type, Seed const& seed); -/** Create a key pair using secure random numbers. */ +/** + * Create a key pair using secure random numbers. + */ std::pair randomKeyPair(KeyType type); -/** Generate a signature for a message digest. - This can only be used with secp256k1 since Ed25519's - security properties come, in part, from how the message - is hashed. -*/ +/** + * Generate a signature for a message digest. + * This can only be used with secp256k1 since Ed25519's + * security properties come, in part, from how the message + * is hashed. + */ /** @{ */ Buffer signDigest(PublicKey const& pk, SecretKey const& sk, uint256 const& digest); @@ -151,10 +166,11 @@ signDigest(KeyType type, SecretKey const& sk, uint256 const& digest) } /** @} */ -/** Generate a signature for a message. - With secp256k1 signatures, the data is first hashed with - SHA512-Half, and the resulting digest is signed. -*/ +/** + * Generate a signature for a message. + * With secp256k1 signatures, the data is first hashed with + * SHA512-Half, and the resulting digest is signed. + */ /** @{ */ Buffer sign(PublicKey const& pk, SecretKey const& sk, Slice const& message); diff --git a/include/xrpl/protocol/Seed.h b/include/xrpl/protocol/Seed.h index a669f52079..4ccdd6707f 100644 --- a/include/xrpl/protocol/Seed.h +++ b/include/xrpl/protocol/Seed.h @@ -12,7 +12,9 @@ namespace xrpl { -/** Seeds are used to generate deterministic secret keys. */ +/** + * Seeds are used to generate deterministic secret keys. + */ class Seed { private: @@ -27,12 +29,15 @@ public: Seed& operator=(Seed const&) = default; - /** Destroy the seed. - The buffer will first be securely erased. - */ + /** + * Destroy the seed. + * The buffer will first be securely erased. + */ ~Seed(); - /** Construct a seed */ + /** + * Construct a seed + */ /** @{ */ explicit Seed(Slice const& slice); explicit Seed(uint128 const& seed); @@ -77,42 +82,52 @@ public: //------------------------------------------------------------------------------ -/** Create a seed using secure random numbers. */ +/** + * Create a seed using secure random numbers. + */ Seed randomSeed(); -/** Generate a seed deterministically. - - The algorithm is specific to the XRPL: - - The seed is calculated as the first 128 bits - of the SHA512-Half of the string text excluding - any terminating null. - - @note This will not attempt to determine the format of - the string (e.g. hex or base58). -*/ +/** + * Generate a seed deterministically. + * + * The algorithm is specific to the XRPL: + * + * The seed is calculated as the first 128 bits + * of the SHA512-Half of the string text excluding + * any terminating null. + * + * @note This will not attempt to determine the format of + * the string (e.g. hex or base58). + */ Seed generateSeed(std::string const& passPhrase); -/** Parse a Base58 encoded string into a seed */ +/** + * Parse a Base58 encoded string into a seed + */ template <> std::optional parseBase58(std::string const& s); -/** Attempt to parse a string as a seed. - - @param str the string to parse - @param rfc1751 true if we should attempt RFC1751 style parsing (deprecated) - * */ +/** + * Attempt to parse a string as a seed. + * + * @param str the string to parse + * @param rfc1751 true if we should attempt RFC1751 style parsing (deprecated) + */ std::optional parseGenericSeed(std::string const& str, bool rfc1751 = true); -/** Encode a Seed in RFC1751 format */ +/** + * Encode a Seed in RFC1751 format + */ std::string seedAs1751(Seed const& seed); -/** Format a seed as a Base58 string */ +/** + * Format a seed as a Base58 string + */ inline std::string toBase58(Seed const& seed) { diff --git a/include/xrpl/protocol/SeqProxy.h b/include/xrpl/protocol/SeqProxy.h index be040cceec..e6a97be0e7 100644 --- a/include/xrpl/protocol/SeqProxy.h +++ b/include/xrpl/protocol/SeqProxy.h @@ -5,33 +5,34 @@ namespace xrpl { -/** A type that represents either a sequence value or a ticket value. - - We use the value() of a SeqProxy in places where a sequence was used - before. An example of this is the sequence of an Offer stored in the - ledger. We do the same thing with the in-ledger identifier of a - Check, Payment Channel, and Escrow. - - Why is this safe? If we use the SeqProxy::value(), how do we know that - each ledger entry will be unique? - - There are two components that make this safe: - - 1. A "TicketCreate" transaction carefully avoids creating a ticket - that corresponds with an already used Sequence or Ticket value. - The transactor does this by referring to the account root's - sequence number. Creating the ticket advances the account root's - sequence number so the same ticket (or sequence) value cannot be - used again. - - 2. When a "TicketCreate" transaction creates a batch of tickets it advances - the account root sequence to one past the largest created ticket. - - Therefore all tickets in a batch other than the first may never have - the same value as a sequence on that same account. And since a ticket - may only be used once there will never be any duplicates within this - account. -*/ +/** + * A type that represents either a sequence value or a ticket value. + * + * We use the value() of a SeqProxy in places where a sequence was used + * before. An example of this is the sequence of an Offer stored in the + * ledger. We do the same thing with the in-ledger identifier of a + * Check, Payment Channel, and Escrow. + * + * Why is this safe? If we use the SeqProxy::value(), how do we know that + * each ledger entry will be unique? + * + * There are two components that make this safe: + * + * 1. A "TicketCreate" transaction carefully avoids creating a ticket + * that corresponds with an already used Sequence or Ticket value. + * The transactor does this by referring to the account root's + * sequence number. Creating the ticket advances the account root's + * sequence number so the same ticket (or sequence) value cannot be + * used again. + * + * 2. When a "TicketCreate" transaction creates a batch of tickets it advances + * the account root sequence to one past the largest created ticket. + * + * Therefore all tickets in a batch other than the first may never have + * the same value as a sequence on that same account. And since a ticket + * may only be used once there will never be any duplicates within this + * account. + */ class SeqProxy { public: @@ -51,7 +52,9 @@ public: SeqProxy& operator=(SeqProxy const& other) = default; - /** Factory function to return a sequence-based SeqProxy */ + /** + * Factory function to return a sequence-based SeqProxy + */ static constexpr SeqProxy sequence(std::uint32_t v) { diff --git a/include/xrpl/protocol/Sign.h b/include/xrpl/protocol/Sign.h index 18f085352d..fad2c35c9e 100644 --- a/include/xrpl/protocol/Sign.h +++ b/include/xrpl/protocol/Sign.h @@ -11,17 +11,18 @@ namespace xrpl { -/** Sign an STObject - - @param st Object to sign - @param prefix Prefix to insert before serialized object when hashing - @param type Signing key type used to derive public key - @param sk Signing secret key - @param sigField Field in which to store the signature on the object. - If not specified the value defaults to `sfSignature`. - - @note If a signature already exists, it is overwritten. -*/ +/** + * Sign an STObject + * + * @param st Object to sign + * @param prefix Prefix to insert before serialized object when hashing + * @param type Signing key type used to derive public key + * @param sk Signing secret key + * @param sigField Field in which to store the signature on the object. + * If not specified the value defaults to `sfSignature`. + * + * @note If a signature already exists, it is overwritten. + */ void sign( STObject& st, @@ -30,14 +31,15 @@ sign( SecretKey const& sk, SF_VL const& sigField = sfSignature); -/** Returns `true` if STObject contains valid signature - - @param st Signed object - @param prefix Prefix inserted before serialized object when hashing - @param pk Public key for verifying signature - @param sigField Object's field containing the signature. - If not specified the value defaults to `sfSignature`. -*/ +/** + * Returns `true` if STObject contains valid signature + * + * @param st Signed object + * @param prefix Prefix inserted before serialized object when hashing + * @param pk Public key for verifying signature + * @param sigField Object's field containing the signature. + * If not specified the value defaults to `sfSignature`. + */ bool verify( STObject const& st, @@ -45,22 +47,25 @@ verify( PublicKey const& pk, SF_VL const& sigField = sfSignature); -/** Return a Serializer suitable for computing a multisigning TxnSignature. */ +/** + * Return a Serializer suitable for computing a multisigning TxnSignature. + */ Serializer buildMultiSigningData(STObject const& obj, AccountID const& signingID); -/** Break the multi-signing hash computation into 2 parts for optimization. - - We can optimize verifying multiple multisignatures by splitting the - data building into two parts; - o A large part that is shared by all of the computations. - o A small part that is unique to each signer in the multisignature. - - The following methods support that optimization: - 1. startMultiSigningData provides the large part which can be shared. - 2. finishMultiSigningData caps the passed in serializer with each - signer's unique data. -*/ +/** + * Break the multi-signing hash computation into 2 parts for optimization. + * + * We can optimize verifying multiple multisignatures by splitting the + * data building into two parts; + * o A large part that is shared by all of the computations. + * o A small part that is unique to each signer in the multisignature. + * + * The following methods support that optimization: + * 1. startMultiSigningData provides the large part which can be shared. + * 2. finishMultiSigningData caps the passed in serializer with each + * signer's unique data. + */ Serializer startMultiSigningData(STObject const& obj); diff --git a/include/xrpl/protocol/SystemParameters.h b/include/xrpl/protocol/SystemParameters.h index b31dd0cd42..6ca36c8d9a 100644 --- a/include/xrpl/protocol/SystemParameters.h +++ b/include/xrpl/protocol/SystemParameters.h @@ -21,22 +21,30 @@ systemName() return kName; } -/** Configure the native currency. */ +/** + * Configure the native currency. + */ -/** Number of drops in the genesis account. */ +/** + * Number of drops in the genesis account. + */ constexpr XRPAmount kInitialXrp{100'000'000'000 * kDropsPerXrp}; static_assert(kInitialXrp.drops() == 100'000'000'000'000'000); static_assert(Number::kMaxRep >= kInitialXrp.drops()); -/** Returns true if the amount does not exceed the initial XRP in existence. */ +/** + * Returns true if the amount does not exceed the initial XRP in existence. + */ inline bool isLegalAmount(XRPAmount const& amount) { return amount <= kInitialXrp; } -/** Returns true if the absolute value of the amount does not exceed the initial - * XRP in existence. */ +/** + * Returns true if the absolute value of the amount does not exceed the initial + * XRP in existence. + */ inline bool isLegalAmountSigned(XRPAmount const& amount) { @@ -51,20 +59,30 @@ systemCurrencyCode() return kCode; } -/** The XRP ledger network's earliest allowed sequence */ +/** + * The XRP ledger network's earliest allowed sequence + */ static constexpr std::uint32_t kXrpLedgerEarliestSeq{32570u}; -/** The XRP Ledger mainnet's earliest ledger with a FeeSettings object. Only - * used in asserts and tests. */ +/** + * The XRP Ledger mainnet's earliest ledger with a FeeSettings object. Only + * used in asserts and tests. + */ static constexpr std::uint32_t kXrpLedgerEarliestFees{562177u}; -/** The minimum amount of support an amendment should have. */ +/** + * The minimum amount of support an amendment should have. + */ constexpr std::ratio<80, 100> kAmendmentMajorityCalcThreshold; -/** The minimum amount of time an amendment must hold a majority */ +/** + * The minimum amount of time an amendment must hold a majority + */ constexpr std::chrono::seconds const kDefaultAmendmentMajorityTime = weeks{2}; } // namespace xrpl -/** Default peer port (IANA registered) */ +/** + * Default peer port (IANA registered) + */ inline constexpr std::uint16_t kDefaultPeerPort{2459}; diff --git a/include/xrpl/protocol/TER.h b/include/xrpl/protocol/TER.h index 54b081f358..730d021254 100644 --- a/include/xrpl/protocol/TER.h +++ b/include/xrpl/protocol/TER.h @@ -225,6 +225,7 @@ enum TERcodes : TERUnderlyingType { // create a pseudo-account terNO_DELEGATE_PERMISSION, // Delegate does not have permission terLOCKED, // MPT is locked + terNO_PERMISSION, // No permission but retry }; //------------------------------------------------------------------------------ @@ -368,6 +369,7 @@ enum TECcodes : TERUnderlyingType { // reclaimed after those networks reset. tecNO_DELEGATE_PERMISSION = 198, tecBAD_PROOF = 199, + tecNO_SPONSOR_PERMISSION = 200, }; //------------------------------------------------------------------------------ diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index 461afd24e7..0afdebb898 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -12,29 +12,30 @@ namespace xrpl { -/** Transaction flags. - - These flags are specified in a transaction's 'Flags' field and modify - the behavior of that transaction. - - There are two types of flags: - - (1) Universal flags: these are flags which apply to, and are interpreted the same way by, - all transactions, except, perhaps, to special pseudo-transactions. - - (2) Tx-Specific flags: these are flags which are interpreted according to the type of the - transaction being executed. That is, the same numerical flag value may have different - effects, depending on the transaction being executed. - - @note The universal transaction flags occupy the high-order 8 bits. - The tx-specific flags occupy the remaining 24 bits. - - @warning Transaction flags form part of the protocol. - **Changing them should be avoided because without special handling, this will result in - a hard fork.** - - @ingroup protocol -*/ +/** + * Transaction flags. + * + * These flags are specified in a transaction's 'Flags' field and modify + * the behavior of that transaction. + * + * There are two types of flags: + * + * (1) Universal flags: these are flags which apply to, and are interpreted the same way by, + * all transactions, except, perhaps, to special pseudo-transactions. + * + * (2) Tx-Specific flags: these are flags which are interpreted according to the type of the + * transaction being executed. That is, the same numerical flag value may have different + * effects, depending on the transaction being executed. + * + * @note The universal transaction flags occupy the high-order 8 bits. + * The tx-specific flags occupy the remaining 24 bits. + * + * @warning Transaction flags form part of the protocol. + * **Changing them should be avoided because without special handling, this will result in + * a hard fork.** + * + * @ingroup protocol + */ using FlagValue = std::uint32_t; @@ -102,7 +103,8 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal; TRANSACTION(Payment, \ TF_FLAG(tfNoRippleDirect, 0x00010000) \ TF_FLAG(tfPartialPayment, 0x00020000) \ - TF_FLAG(tfLimitQuality, 0x00040000), \ + TF_FLAG(tfLimitQuality, 0x00040000) \ + TF_FLAG(tfSponsorCreatedAccount, 0x00080000), \ MASK_ADJ(0)) \ \ TRANSACTION(TrustSet, \ @@ -141,7 +143,7 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal; TF_FLAG(tfMPTCanTrade, lsfMPTCanTrade) \ TF_FLAG(tfMPTCanTransfer, lsfMPTCanTransfer) \ TF_FLAG(tfMPTCanClawback, lsfMPTCanClawback) \ - TF_FLAG(tfMPTCanHoldConfidentialBalance, lsfMPTCanHoldConfidentialBalance), \ + TF_FLAG(tfMPTCanHoldConfidentialBalance, lsfMPTCanHoldConfidentialBalance), \ MASK_ADJ(0)) \ \ TRANSACTION(MPTokenAuthorize, \ @@ -215,6 +217,20 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal; TF_FLAG(tfLoanDefault, 0x00010000) \ TF_FLAG(tfLoanImpair, 0x00020000) \ TF_FLAG(tfLoanUnimpair, 0x00040000), \ + MASK_ADJ(0)) \ + \ + TRANSACTION(SponsorshipSet, \ + TF_FLAG(tfSponsorshipSetRequireSignForFee, 0x00010000) \ + TF_FLAG(tfSponsorshipClearRequireSignForFee, 0x00020000) \ + TF_FLAG(tfSponsorshipSetRequireSignForReserve, 0x00040000) \ + TF_FLAG(tfSponsorshipClearRequireSignForReserve, 0x00080000) \ + TF_FLAG(tfDeleteObject, 0x00100000), \ + MASK_ADJ(0)) \ + \ + TRANSACTION(SponsorshipTransfer, \ + TF_FLAG(tfSponsorshipEnd, 0x00010000) \ + TF_FLAG(tfSponsorshipCreate, 0x00020000) \ + TF_FLAG(tfSponsorshipReassign, 0x00040000), \ MASK_ADJ(0)) // clang-format on @@ -444,6 +460,12 @@ getAsfFlagMap() #pragma pop_macro("ACCOUNTSET_FLAG_TO_MAP") #pragma pop_macro("ACCOUNTSET_FLAGS") +// Sponsor flags (spf) + +inline constexpr FlagValue spfSponsorFee = 1; +inline constexpr FlagValue spfSponsorReserve = 2; +inline constexpr FlagValue spfSponsorFlagMask = ~(spfSponsorFee | spfSponsorReserve); + } // namespace xrpl // NOLINTEND(readability-identifier-naming) diff --git a/include/xrpl/protocol/TxFormats.h b/include/xrpl/protocol/TxFormats.h index 36eb6d0889..8fb32c93cb 100644 --- a/include/xrpl/protocol/TxFormats.h +++ b/include/xrpl/protocol/TxFormats.h @@ -8,34 +8,36 @@ namespace xrpl { -/** Transaction type identifiers. - - These are part of the binary message format. - - @ingroup protocol -*/ -/** Transaction type identifiers - - Each ledger object requires a unique type identifier, which is stored - within the object itself; this makes it possible to iterate the entire - ledger and determine each object's type and verify that the object you - retrieved from a given hash matches the expected type. - - @warning Since these values are included in transactions, which are signed - objects, and used by the code to determine the type of transaction - being invoked, they are part of the protocol. **Changing them - should be avoided because without special handling, this will - result in a hard fork.** - - @note When retiring types, the specific values should not be removed but - should be marked as [[deprecated]]. This is to avoid accidental - reuse of identifiers. - - @todo The C++ language does not enable checking for duplicate values - here. If it becomes possible then we should do this. - - @ingroup protocol -*/ +/** + * Transaction type identifiers. + * + * These are part of the binary message format. + * + * @ingroup protocol + */ +/** + * Transaction type identifiers + * + * Each ledger object requires a unique type identifier, which is stored + * within the object itself; this makes it possible to iterate the entire + * ledger and determine each object's type and verify that the object you + * retrieved from a given hash matches the expected type. + * + * @warning Since these values are included in transactions, which are signed + * objects, and used by the code to determine the type of transaction + * being invoked, they are part of the protocol. **Changing them + * should be avoided because without special handling, this will + * result in a hard fork.** + * + * @note When retiring types, the specific values should not be removed but + * should be marked as [[deprecated]]. This is to avoid accidental + * reuse of identifiers. + * + * @todo The C++ language does not enable checking for duplicate values + * here. If it becomes possible then we should do this. + * + * @ingroup protocol + */ // clang-format off // Protocol-critical, hundreds of usages // NOLINTNEXTLINE(cppcoreguidelines-use-enum-class) @@ -52,28 +54,38 @@ enum TxType : std::uint16_t #undef TRANSACTION #pragma pop_macro("TRANSACTION") - /** This transaction type is deprecated; it is retained for historical purposes. */ + /** + * This transaction type is deprecated; it is retained for historical purposes. + */ TtNicknameSet [[deprecated("This transaction type is not supported and should not be used.")]] = 6, - /** This transaction type is deprecated; it is retained for historical purposes. */ + /** + * This transaction type is deprecated; it is retained for historical purposes. + */ TtContract [[deprecated("This transaction type is not supported and should not be used.")]] = 9, - /** This identifier was never used, but the slot is reserved for historical purposes. */ + /** + * This identifier was never used, but the slot is reserved for historical purposes. + */ TtSpinalTap [[deprecated("This transaction type is not supported and should not be used.")]] = 11, - /** This transaction type installs a hook. */ + /** + * This transaction type installs a hook. + */ TtHookSet [[maybe_unused]] = 22, }; // clang-format on -/** Manages the list of known transaction formats. +/** + * Manages the list of known transaction formats. */ class TxFormats : public KnownFormats { private: - /** Create the object. - This will load the object with all the known transaction formats. - */ + /** + * Create the object. + * This will load the object with all the known transaction formats. + */ TxFormats(); public: diff --git a/include/xrpl/protocol/TxMeta.h b/include/xrpl/protocol/TxMeta.h index 813f1b1615..88652afba9 100644 --- a/include/xrpl/protocol/TxMeta.h +++ b/include/xrpl/protocol/TxMeta.h @@ -60,7 +60,9 @@ public: STObject& getAffectedNode(uint256 const&); - /** Return a list of accounts affected by this transaction */ + /** + * Return a list of accounts affected by this transaction + */ [[nodiscard]] boost::container::flat_set getAffectedAccounts() const; diff --git a/include/xrpl/protocol/UintTypes.h b/include/xrpl/protocol/UintTypes.h index 1a3cb96691..25b0d1ffc9 100644 --- a/include/xrpl/protocol/UintTypes.h +++ b/include/xrpl/protocol/UintTypes.h @@ -30,34 +30,50 @@ public: } // namespace detail -/** Directory is an index into the directory of offer books. - The last 64 bits of this are the quality. */ +/** + * Directory is an index into the directory of offer books. + * The last 64 bits of this are the quality. + */ using Directory = BaseUInt<256, detail::DirectoryTag>; -/** Currency is a hash representing a specific currency. */ +/** + * Currency is a hash representing a specific currency. + */ using Currency = BaseUInt<160, detail::CurrencyTag>; -/** NodeID is a 160-bit hash representing one node. */ +/** + * NodeID is a 160-bit hash representing one node. + */ using NodeID = BaseUInt<160, detail::NodeIDTag>; -/** MPTID is a 192-bit value representing MPT Issuance ID, +/** + * MPTID is a 192-bit value representing MPT Issuance ID, * which is a concatenation of a 32-bit sequence (big endian) - * and a 160-bit account */ + * and a 160-bit account + */ using MPTID = BaseUInt<192>; -/** Domain is a 256-bit hash representing a specific domain. */ +/** + * Domain is a 256-bit hash representing a specific domain. + */ using Domain = BaseUInt<256>; -/** XRP currency. */ +/** + * XRP currency. + */ Currency const& xrpCurrency(); -/** A placeholder for empty currencies. */ +/** + * A placeholder for empty currencies. + */ Currency const& noCurrency(); -/** We deliberately disallow the currency that looks like "XRP" because too - many people were using it instead of the correct XRP currency. */ +/** + * We deliberately disallow the currency that looks like "XRP" because too + * many people were using it instead of the correct XRP currency. + */ Currency const& badCurrency(); @@ -67,26 +83,30 @@ isXRP(Currency const& c) return c == beast::kZero; } -/** Returns "", "XRP", or three letter ISO code. */ +/** + * Returns "", "XRP", or three letter ISO code. + */ std::string to_string(Currency const& c); -/** Tries to convert a string to a Currency, returns true on success. - - @note This function will return success if the resulting currency is - badCurrency(). This legacy behavior is unfortunate; changing this - will require very careful checking everywhere and may mean having - to rewrite some unit test code. -*/ +/** + * Tries to convert a string to a Currency, returns true on success. + * + * @note This function will return success if the resulting currency is + * badCurrency(). This legacy behavior is unfortunate; changing this + * will require very careful checking everywhere and may mean having + * to rewrite some unit test code. + */ bool toCurrency(Currency&, std::string const&); -/** Tries to convert a string to a Currency, returns noCurrency() on failure. - - @note This function can return badCurrency(). This legacy behavior is - unfortunate; changing this will require very careful checking - everywhere and may mean having to rewrite some unit test code. -*/ +/** + * Tries to convert a string to a Currency, returns noCurrency() on failure. + * + * @note This function can return badCurrency(). This legacy behavior is + * unfortunate; changing this will require very careful checking + * everywhere and may mean having to rewrite some unit test code. + */ Currency toCurrency(std::string const&); diff --git a/include/xrpl/protocol/Units.h b/include/xrpl/protocol/Units.h index 8b418704d6..169ee2c543 100644 --- a/include/xrpl/protocol/Units.h +++ b/include/xrpl/protocol/Units.h @@ -20,18 +20,26 @@ namespace xrpl { namespace unit { -/** "drops" are the smallest divisible amount of XRP. This is what most - of the code uses. */ +/** + * "drops" are the smallest divisible amount of XRP. This is what most + * of the code uses. + */ struct dropTag; -/** "fee levels" are used by the transaction queue to compare the relative - cost of transactions that require different levels of effort to process. - See also: src/xrpld/app/misc/FeeEscalation.md#fee-level */ +/** + * "fee levels" are used by the transaction queue to compare the relative + * cost of transactions that require different levels of effort to process. + * See also: src/xrpld/app/misc/FeeEscalation.md#fee-level + */ struct feelevelTag; -/** unitless values are plain scalars wrapped in a ValueUnit. They are - used for calculations in this header. */ +/** + * unitless values are plain scalars wrapped in a ValueUnit. They are + * used for calculations in this header. + */ struct unitlessTag; -/** Units to represent basis points (bips) and 1/10 basis points */ +/** + * Units to represent basis points (bips) and 1/10 basis points + */ class BipsTag; class TenthBipsTag; @@ -42,13 +50,14 @@ template concept Valid = std::is_class_v && std::is_object_v && std::is_object_v; -/** `Usable` is checked to ensure that only values with - known valid type tags can be used (sometimes transparently) in - non-unit contexts. At the time of implementation, this includes - all known tags, but more may be added in the future, and they - should not be added automatically unless determined to be - appropriate. -*/ +/** + * `Usable` is checked to ensure that only values with + * known valid type tags can be used (sometimes transparently) in + * non-unit contexts. At the time of implementation, this includes + * all known tags, but more may be added in the future, and they + * should not be added automatically unless determined to be + * appropriate. + */ template concept Usable = Valid && (std::is_same_v || @@ -115,9 +124,11 @@ public: return *this; } - /** Instances with the same unit, and a type that is - "safe" to convert to this one can be converted - implicitly */ + /** + * Instances with the same unit, and a type that is + * "safe" to convert to this one can be converted + * implicitly + */ template Other> constexpr ValueUnit(ValueUnit const& value) requires SafeToCast @@ -260,14 +271,18 @@ public: return value_ < other.value_; } - /** Returns true if the amount is not zero */ + /** + * Returns true if the amount is not zero + */ explicit constexpr operator bool() const noexcept { return value_ != 0; } - /** Return the sign of the amount */ + /** + * Return the sign of the amount + */ [[nodiscard]] constexpr int signum() const noexcept { @@ -276,7 +291,9 @@ public: return value_ ? 1 : 0; } - /** Returns the number of drops */ + /** + * Returns the number of drops + */ // TODO: Move this to a new class, maybe with the old "TaggedFee" name [[nodiscard]] constexpr value_type fee() const @@ -319,10 +336,11 @@ public: } } - /** Returns the underlying value. Code SHOULD NOT call this - function unless the type has been abstracted away, - e.g. in a templated function. - */ + /** + * Returns the underlying value. Code SHOULD NOT call this + * function unless the type has been abstracted away, + * e.g. in a templated function. + */ [[nodiscard]] constexpr value_type value() const { diff --git a/include/xrpl/protocol/XRPAmount.h b/include/xrpl/protocol/XRPAmount.h index 3190980ecb..26e10929df 100644 --- a/include/xrpl/protocol/XRPAmount.h +++ b/include/xrpl/protocol/XRPAmount.h @@ -138,7 +138,9 @@ public: return drops_ < other.drops_; } - /** Returns true if the amount is not zero */ + /** + * Returns true if the amount is not zero + */ explicit constexpr operator bool() const noexcept { @@ -150,7 +152,9 @@ public: return drops(); } - /** Return the sign of the amount */ + /** + * Return the sign of the amount + */ [[nodiscard]] constexpr int signum() const noexcept { @@ -159,7 +163,9 @@ public: return (drops_ != 0) ? 1 : 0; } - /** Returns the number of drops */ + /** + * Returns the number of drops + */ [[nodiscard]] constexpr value_type drops() const { @@ -217,10 +223,11 @@ public: return static_cast(drops_); } - /** Returns the underlying value. Code SHOULD NOT call this - function unless the type has been abstracted away, - e.g. in a templated function. - */ + /** + * Returns the underlying value. Code SHOULD NOT call this + * function unless the type has been abstracted away, + * e.g. in a templated function. + */ [[nodiscard]] constexpr value_type value() const { @@ -241,7 +248,9 @@ public: } }; -/** Number of drops per 1 XRP */ +/** + * Number of drops per 1 XRP + */ constexpr XRPAmount kDropsPerXrp{1'000'000}; constexpr double diff --git a/include/xrpl/protocol/detail/STVar.h b/include/xrpl/protocol/detail/STVar.h index 6853166174..12026f3d09 100644 --- a/include/xrpl/protocol/detail/STVar.h +++ b/include/xrpl/protocol/detail/STVar.h @@ -50,14 +50,13 @@ public: STVar& operator=(STVar&& rhs); - STVar(STBase&& t) // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved) + // NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved) + STVar(STBase&& t) : p_(t.move(kMaxSize, &d_)) { - p_ = t.move(kMaxSize, &d_); } - STVar(STBase const& t) + STVar(STBase const& t) : p_(t.copy(kMaxSize, &d_)) { - p_ = t.copy(kMaxSize, &d_); } STVar(DefaultObjectT, SField const& name); @@ -121,7 +120,8 @@ private: } } - /** Construct requested Serializable Type according to id. + /** + * Construct requested Serializable Type according to id. * The variadic args are: (SField), or (SerialIter, SField). * depth is ignored in former case. */ diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 3f078561a3..bfe03a6303 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -15,6 +15,7 @@ // Add new amendments to the top of this list. // Keep it sorted in reverse chronological order. +XRPL_FEATURE(Sponsor, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FEATURE(BatchV1_1, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FEATURE(LendingProtocolV1_1, Supported::No, VoteBehavior::DefaultNo) XRPL_FEATURE(ConfidentialTransfer, Supported::Yes, VoteBehavior::DefaultNo) diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index 2f403708c3..90810e06d2 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -127,29 +127,32 @@ LEDGER_ENTRY(ltTICKET, 0x0054, Ticket, ticket, ({ \sa keylet::account */ LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, account, ({ - {sfAccount, SoeRequired}, - {sfSequence, SoeRequired}, - {sfBalance, SoeRequired}, - {sfOwnerCount, SoeRequired}, - {sfPreviousTxnID, SoeRequired}, - {sfPreviousTxnLgrSeq, SoeRequired}, - {sfAccountTxnID, SoeOptional}, - {sfRegularKey, SoeOptional}, - {sfEmailHash, SoeOptional}, - {sfWalletLocator, SoeOptional}, - {sfWalletSize, SoeOptional}, - {sfMessageKey, SoeOptional}, - {sfTransferRate, SoeOptional}, - {sfDomain, SoeOptional}, - {sfTickSize, SoeOptional}, - {sfTicketCount, SoeOptional}, - {sfNFTokenMinter, SoeOptional}, - {sfMintedNFTokens, SoeDefault}, - {sfBurnedNFTokens, SoeDefault}, - {sfFirstNFTokenSequence, SoeOptional}, - {sfAMMID, SoeOptional}, // pseudo-account designator - {sfVaultID, SoeOptional}, // pseudo-account designator - {sfLoanBrokerID, SoeOptional}, // pseudo-account designator + {sfAccount, SoeRequired}, + {sfSequence, SoeRequired}, + {sfBalance, SoeRequired}, + {sfOwnerCount, SoeRequired}, + {sfPreviousTxnID, SoeRequired}, + {sfPreviousTxnLgrSeq, SoeRequired}, + {sfAccountTxnID, SoeOptional}, + {sfRegularKey, SoeOptional}, + {sfEmailHash, SoeOptional}, + {sfWalletLocator, SoeOptional}, + {sfWalletSize, SoeOptional}, + {sfMessageKey, SoeOptional}, + {sfTransferRate, SoeOptional}, + {sfDomain, SoeOptional}, + {sfTickSize, SoeOptional}, + {sfTicketCount, SoeOptional}, + {sfNFTokenMinter, SoeOptional}, + {sfMintedNFTokens, SoeDefault}, + {sfBurnedNFTokens, SoeDefault}, + {sfFirstNFTokenSequence, SoeOptional}, + {sfSponsoredOwnerCount, SoeDefault}, + {sfSponsoringOwnerCount, SoeDefault}, + {sfSponsoringAccountCount, SoeDefault}, + {sfAMMID, SoeOptional}, // pseudo-account designator + {sfVaultID, SoeOptional}, // pseudo-account designator + {sfLoanBrokerID, SoeOptional}, // pseudo-account designator })) /** A ledger object which contains a list of object identifiers. @@ -286,6 +289,8 @@ LEDGER_ENTRY(ltRIPPLE_STATE, 0x0072, RippleState, state, ({ {sfHighNode, SoeOptional}, {sfHighQualityIn, SoeOptional}, {sfHighQualityOut, SoeOptional}, + {sfHighSponsor, SoeOptional}, + {sfLowSponsor, SoeOptional}, })) /** The ledger object which lists the network's fee settings. @@ -616,5 +621,20 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({ {sfLoanScale, SoeDefault}, })) +/** A ledger object representing a sponsorship. + \sa keylet::sponsorship + */ +LEDGER_ENTRY(ltSPONSORSHIP, 0x0090, Sponsorship, sponsorship, ({ + {sfPreviousTxnID, SoeRequired}, + {sfPreviousTxnLgrSeq, SoeRequired}, + {sfOwner, SoeRequired}, + {sfSponsee, SoeRequired}, + {sfFeeAmount, SoeOptional}, + {sfMaxFee, SoeOptional}, + {sfRemainingOwnerCount, SoeDefault}, + {sfOwnerNode, SoeRequired}, + {sfSponseeNode, SoeRequired}, +})) + #undef EXPAND #undef LEDGER_ENTRY_DUPLICATE diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index 0d453eea11..4ef76c8b75 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -114,6 +114,11 @@ TYPED_SFIELD(sfLateInterestRate, UINT32, 66) // 1/10 basis points (bi TYPED_SFIELD(sfCloseInterestRate, UINT32, 67) // 1/10 basis points (bips) TYPED_SFIELD(sfOverpaymentInterestRate, UINT32, 68) // 1/10 basis points (bips) TYPED_SFIELD(sfConfidentialBalanceVersion, UINT32, 69) +TYPED_SFIELD(sfSponsoredOwnerCount, UINT32, 70) +TYPED_SFIELD(sfSponsoringOwnerCount, UINT32, 71) +TYPED_SFIELD(sfSponsoringAccountCount, UINT32, 72) +TYPED_SFIELD(sfRemainingOwnerCount, UINT32, 73) +TYPED_SFIELD(sfSponsorFlags, UINT32, 74) // 64-bit integers (common) TYPED_SFIELD(sfIndexNext, UINT64, 1) @@ -148,6 +153,7 @@ TYPED_SFIELD(sfLockedAmount, UINT64, 29, SField::kSmdBaseTen|SFie TYPED_SFIELD(sfVaultNode, UINT64, 30) TYPED_SFIELD(sfLoanBrokerNode, UINT64, 31) TYPED_SFIELD(sfConfidentialOutstandingAmount, UINT64, 32, SField::kSmdBaseTen|SField::kSmdDefault) +TYPED_SFIELD(sfSponseeNode, UINT64, 33) // 128-bit TYPED_SFIELD(sfEmailHash, UINT128, 1) @@ -209,6 +215,7 @@ TYPED_SFIELD(sfLoanBrokerID, UINT256, 37, TYPED_SFIELD(sfLoanID, UINT256, 38) TYPED_SFIELD(sfReferenceHolding, UINT256, 39) TYPED_SFIELD(sfBlindingFactor, UINT256, 40) +TYPED_SFIELD(sfObjectID, UINT256, 41) // number (common) TYPED_SFIELD(sfNumber, NUMBER, 1) @@ -268,6 +275,8 @@ TYPED_SFIELD(sfPrice, AMOUNT, 28) TYPED_SFIELD(sfSignatureReward, AMOUNT, 29) TYPED_SFIELD(sfMinAccountCreateAmount, AMOUNT, 30) TYPED_SFIELD(sfLPTokenBalance, AMOUNT, 31) +TYPED_SFIELD(sfFeeAmount, AMOUNT, 32) +TYPED_SFIELD(sfMaxFee, AMOUNT, 33) // variable length (common) TYPED_SFIELD(sfPublicKey, VL, 1) @@ -343,6 +352,11 @@ TYPED_SFIELD(sfIssuingChainDoor, ACCOUNT, 23) TYPED_SFIELD(sfSubject, ACCOUNT, 24) TYPED_SFIELD(sfBorrower, ACCOUNT, 25) TYPED_SFIELD(sfCounterparty, ACCOUNT, 26) +TYPED_SFIELD(sfSponsor, ACCOUNT, 27) +TYPED_SFIELD(sfHighSponsor, ACCOUNT, 28) +TYPED_SFIELD(sfLowSponsor, ACCOUNT, 29) +TYPED_SFIELD(sfCounterpartySponsor, ACCOUNT, 30) +TYPED_SFIELD(sfSponsee, ACCOUNT, 31) // vector of 256-bit TYPED_SFIELD(sfIndexes, VECTOR256, 1, SField::kSmdNever) @@ -407,6 +421,7 @@ UNTYPED_SFIELD(sfRawTransaction, OBJECT, 34) UNTYPED_SFIELD(sfBatchSigner, OBJECT, 35) UNTYPED_SFIELD(sfBook, OBJECT, 36) UNTYPED_SFIELD(sfCounterpartySignature, OBJECT, 37, SField::kSmdDefault, SField::kNotSigning) +UNTYPED_SFIELD(sfSponsorSignature, OBJECT, 38, SField::kSmdDefault, SField::kNotSigning) // array of objects (common) // ARRAY/1 is reserved for end of array diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index b0c4f66bae..e805596c00 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -1165,6 +1165,35 @@ TRANSACTION(ttCONFIDENTIAL_MPT_CLAWBACK, 89, ConfidentialMPTClawback, {sfZKProof, SoeRequired}, })) +/** This transaction transfers sponsorship on an object/account. */ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttSPONSORSHIP_TRANSFER, 90, SponsorshipTransfer, + Delegation::NotDelegable, + featureSponsor, + NoPriv, + ({ + {sfObjectID, SoeOptional}, + {sfSponsee, SoeOptional}, +})) + +/** This transaction creates a Sponsorship object. */ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttSPONSORSHIP_SET, 91, SponsorshipSet, + Delegation::Delegable, + featureSponsor, + NoPriv, + ({ + {sfCounterpartySponsor, SoeOptional}, + {sfSponsee, SoeOptional}, + {sfFeeAmount, SoeOptional}, + {sfMaxFee, SoeOptional}, + {sfRemainingOwnerCount, SoeOptional}, +})) + /** This system-generated transaction type is used to update the status of the various amendments. For details, see: https://xrpl.org/amendments.html diff --git a/include/xrpl/protocol/digest.h b/include/xrpl/protocol/digest.h index c1e70cada2..44fca3d1ea 100644 --- a/include/xrpl/protocol/digest.h +++ b/include/xrpl/protocol/digest.h @@ -12,21 +12,23 @@ namespace xrpl { -/** Message digest functions used in the codebase - - @note These are modeled to meet the requirements of `Hasher` in the - `hash_append` interface, discussed in proposal: - - N3980 "Types Don't Know #" - http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3980.html -*/ +/** + * Message digest functions used in the codebase + * + * @note These are modeled to meet the requirements of `Hasher` in the + * `hash_append` interface, discussed in proposal: + * + * N3980 "Types Don't Know #" + * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3980.html + */ //------------------------------------------------------------------------------ -/** RIPEMD-160 digest - - @note This uses the OpenSSL implementation -*/ +/** + * RIPEMD-160 digest + * + * @note This uses the OpenSSL implementation + */ struct OpensslRipemd160Hasher { public: @@ -46,10 +48,11 @@ private: char ctx_[96]{}; }; -/** SHA-512 digest - - @note This uses the OpenSSL implementation -*/ +/** + * SHA-512 digest + * + * @note This uses the OpenSSL implementation + */ struct OpensslSha512Hasher { public: @@ -69,10 +72,11 @@ private: char ctx_[216]{}; }; -/** SHA-256 digest - - @note This uses the OpenSSL implementation -*/ +/** + * SHA-256 digest + * + * @note This uses the OpenSSL implementation + */ struct OpensslSha256Hasher { public: @@ -100,21 +104,22 @@ using sha512_hasher = OpensslSha512Hasher; //------------------------------------------------------------------------------ -/** Returns the RIPEMD-160 digest of the SHA256 hash of the message. - - This operation is used to compute the 160-bit identifier - representing an XRPL account, from a message. Typically the - message is the public key of the account - which is not - stored in the account root. - - The same computation is used regardless of the cryptographic - scheme implied by the public key. For example, the public key - may be an ed25519 public key or a secp256k1 public key. Support - for new cryptographic systems may be added, using the same - formula for calculating the account identifier. - - Meets the requirements of Hasher (in hash_append) -*/ +/** + * Returns the RIPEMD-160 digest of the SHA256 hash of the message. + * + * This operation is used to compute the 160-bit identifier + * representing an XRPL account, from a message. Typically the + * message is the public key of the account - which is not + * stored in the account root. + * + * The same computation is used regardless of the cryptographic + * scheme implied by the public key. For example, the public key + * may be an ed25519 public key or a secp256k1 public key. Support + * for new cryptographic systems may be added, using the same + * formula for calculating the account identifier. + * + * Meets the requirements of Hasher (in hash_append) + */ struct RipeshaHasher { private: @@ -145,11 +150,12 @@ public: namespace detail { -/** Returns the SHA512-Half digest of a message. - - The SHA512-Half is the first 256 bits of the - SHA-512 digest of the message. -*/ +/** + * Returns the SHA512-Half digest of a message. + * + * The SHA512-Half is the first 256 bits of the + * SHA-512 digest of the message. + */ template struct BasicSha512HalfHasher { @@ -201,7 +207,9 @@ using sha512_half_hasher_s = detail::BasicSha512HalfHasher; //------------------------------------------------------------------------------ -/** Returns the SHA512-Half of a series of objects. */ +/** + * Returns the SHA512-Half of a series of objects. + */ template sha512_half_hasher::result_type sha512Half(Args const&... args) @@ -212,12 +220,13 @@ sha512Half(Args const&... args) return static_cast(h); } -/** Returns the SHA512-Half of a series of objects. - - Postconditions: - Temporary memory storing copies of - input messages will be cleared. -*/ +/** + * Returns the SHA512-Half of a series of objects. + * + * Postconditions: + * Temporary memory storing copies of + * input messages will be cleared. + */ template sha512_half_hasher_s::result_type sha512HalfS(Args const&... args) diff --git a/include/xrpl/protocol/jss.h b/include/xrpl/protocol/jss.h index 191ed385f3..b20b71661a 100644 --- a/include/xrpl/protocol/jss.h +++ b/include/xrpl/protocol/jss.h @@ -558,6 +558,9 @@ JSS(source_account); // in: PathRequest, RipplePathFind JSS(source_amount); // in: PathRequest, RipplePathFind JSS(source_currencies); // in: PathRequest, RipplePathFind JSS(source_tag); // out: AccountChannels +JSS(sponsee); // in: LedgerEntry +JSS(sponsor); // in: LedgerEntry +JSS(sponsored); // in: AccountObjects JSS(stand_alone); // out: NetworkOPs JSS(standard_deviation); // out: get_aggregate_price JSS(start); // in: TxHistory diff --git a/include/xrpl/protocol/serialize.h b/include/xrpl/protocol/serialize.h index e758b57d49..130a2366cb 100644 --- a/include/xrpl/protocol/serialize.h +++ b/include/xrpl/protocol/serialize.h @@ -9,7 +9,9 @@ namespace xrpl { -/** Serialize an object to a blob. */ +/** + * Serialize an object to a blob. + */ template Blob serializeBlob(Object const& o) @@ -19,7 +21,9 @@ serializeBlob(Object const& o) return s.peekData(); } -/** Serialize an object to a hex string. */ +/** + * Serialize an object to a hex string. + */ inline std::string serializeHex(STObject const& o) { diff --git a/include/xrpl/protocol/tokens.h b/include/xrpl/protocol/tokens.h index bce7e7b5d6..0ce80030e6 100644 --- a/include/xrpl/protocol/tokens.h +++ b/include/xrpl/protocol/tokens.h @@ -35,17 +35,18 @@ template [[nodiscard]] std::optional parseBase58(TokenType type, std::string const& s); -/** Encode data in Base58Check format using XRPL alphabet - - For details on the format see - https://xrpl.org/base58-encodings.html#base58-encodings - - @param type The type of token to encode. - @param token Pointer to the data to encode. - @param size The size of the data to encode. - - @return the encoded token. -*/ +/** + * Encode data in Base58Check format using XRPL alphabet + * + * For details on the format see + * https://xrpl.org/base58-encodings.html#base58-encodings + * + * @param type The type of token to encode. + * @param token Pointer to the data to encode. + * @param size The size of the data to encode. + * + * @return the encoded token. + */ [[nodiscard]] std::string encodeBase58Token(TokenType type, void const* token, std::size_t size); diff --git a/include/xrpl/protocol_autogen/LedgerEntryBase.h b/include/xrpl/protocol_autogen/LedgerEntryBase.h index 5758adbb24..7902055b85 100644 --- a/include/xrpl/protocol_autogen/LedgerEntryBase.h +++ b/include/xrpl/protocol_autogen/LedgerEntryBase.h @@ -158,7 +158,9 @@ public: } protected: - /** @brief The underlying serialized ledger entry being wrapped. */ + /** + * @brief The underlying serialized ledger entry being wrapped. + */ SLE::const_pointer sle_; }; diff --git a/include/xrpl/protocol_autogen/TransactionBase.h b/include/xrpl/protocol_autogen/TransactionBase.h index 161d718c66..a135709576 100644 --- a/include/xrpl/protocol_autogen/TransactionBase.h +++ b/include/xrpl/protocol_autogen/TransactionBase.h @@ -450,7 +450,9 @@ public: } protected: - /** @brief The underlying transaction object being wrapped. */ + /** + * @brief The underlying transaction object being wrapped. + */ std::shared_ptr tx_; }; diff --git a/include/xrpl/protocol_autogen/ledger_entries/AMM.h b/include/xrpl/protocol_autogen/ledger_entries/AMM.h index 11fd3738c9..e6daaf0043 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/AMM.h +++ b/include/xrpl/protocol_autogen/ledger_entries/AMM.h @@ -265,7 +265,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/AccountRoot.h b/include/xrpl/protocol_autogen/ledger_entries/AccountRoot.h index f9a12a027f..04249b4e04 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/AccountRoot.h +++ b/include/xrpl/protocol_autogen/ledger_entries/AccountRoot.h @@ -447,6 +447,78 @@ public: return this->sle_->isFieldPresent(sfFirstNFTokenSequence); } + /** + * @brief Get sfSponsoredOwnerCount (SoeDefault) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getSponsoredOwnerCount() const + { + if (hasSponsoredOwnerCount()) + return this->sle_->at(sfSponsoredOwnerCount); + return std::nullopt; + } + + /** + * @brief Check if sfSponsoredOwnerCount is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasSponsoredOwnerCount() const + { + return this->sle_->isFieldPresent(sfSponsoredOwnerCount); + } + + /** + * @brief Get sfSponsoringOwnerCount (SoeDefault) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getSponsoringOwnerCount() const + { + if (hasSponsoringOwnerCount()) + return this->sle_->at(sfSponsoringOwnerCount); + return std::nullopt; + } + + /** + * @brief Check if sfSponsoringOwnerCount is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasSponsoringOwnerCount() const + { + return this->sle_->isFieldPresent(sfSponsoringOwnerCount); + } + + /** + * @brief Get sfSponsoringAccountCount (SoeDefault) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getSponsoringAccountCount() const + { + if (hasSponsoringAccountCount()) + return this->sle_->at(sfSponsoringAccountCount); + return std::nullopt; + } + + /** + * @brief Check if sfSponsoringAccountCount is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasSponsoringAccountCount() const + { + return this->sle_->isFieldPresent(sfSponsoringAccountCount); + } + /** * @brief Get sfAMMID (SoeOptional) * @return The field value, or std::nullopt if not present. @@ -564,7 +636,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) @@ -786,6 +860,39 @@ public: return *this; } + /** + * @brief Set sfSponsoredOwnerCount (SoeDefault) + * @return Reference to this builder for method chaining. + */ + AccountRootBuilder& + setSponsoredOwnerCount(std::decay_t const& value) + { + object_[sfSponsoredOwnerCount] = value; + return *this; + } + + /** + * @brief Set sfSponsoringOwnerCount (SoeDefault) + * @return Reference to this builder for method chaining. + */ + AccountRootBuilder& + setSponsoringOwnerCount(std::decay_t const& value) + { + object_[sfSponsoringOwnerCount] = value; + return *this; + } + + /** + * @brief Set sfSponsoringAccountCount (SoeDefault) + * @return Reference to this builder for method chaining. + */ + AccountRootBuilder& + setSponsoringAccountCount(std::decay_t const& value) + { + object_[sfSponsoringAccountCount] = value; + return *this; + } + /** * @brief Set sfAMMID (SoeOptional) * @return Reference to this builder for method chaining. diff --git a/include/xrpl/protocol_autogen/ledger_entries/Amendments.h b/include/xrpl/protocol_autogen/ledger_entries/Amendments.h index 6a801308ca..c8b6c2d524 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Amendments.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Amendments.h @@ -175,7 +175,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAmendments (SoeOptional) diff --git a/include/xrpl/protocol_autogen/ledger_entries/Bridge.h b/include/xrpl/protocol_autogen/ledger_entries/Bridge.h index 2c7479b243..31d0e50c02 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Bridge.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Bridge.h @@ -219,7 +219,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/Check.h b/include/xrpl/protocol_autogen/ledger_entries/Check.h index 5b3fd10b92..d354425c39 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Check.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Check.h @@ -278,7 +278,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/Credential.h b/include/xrpl/protocol_autogen/ledger_entries/Credential.h index dfce76e45c..f4d243aea8 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Credential.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Credential.h @@ -228,7 +228,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfSubject (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/DID.h b/include/xrpl/protocol_autogen/ledger_entries/DID.h index ad423377e7..71113862c5 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/DID.h +++ b/include/xrpl/protocol_autogen/ledger_entries/DID.h @@ -202,7 +202,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/Delegate.h b/include/xrpl/protocol_autogen/ledger_entries/Delegate.h index bfe5f5587a..84a33ea2be 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Delegate.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Delegate.h @@ -181,7 +181,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/DepositPreauth.h b/include/xrpl/protocol_autogen/ledger_entries/DepositPreauth.h index 069bed6b77..c4c4434251 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/DepositPreauth.h +++ b/include/xrpl/protocol_autogen/ledger_entries/DepositPreauth.h @@ -179,7 +179,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/DirectoryNode.h b/include/xrpl/protocol_autogen/ledger_entries/DirectoryNode.h index 50659c33f6..7879f104af 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/DirectoryNode.h +++ b/include/xrpl/protocol_autogen/ledger_entries/DirectoryNode.h @@ -440,7 +440,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfOwner (SoeOptional) diff --git a/include/xrpl/protocol_autogen/ledger_entries/Escrow.h b/include/xrpl/protocol_autogen/ledger_entries/Escrow.h index f3c033d26d..106d69722f 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Escrow.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Escrow.h @@ -372,7 +372,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/FeeSettings.h b/include/xrpl/protocol_autogen/ledger_entries/FeeSettings.h index 8f43d3b782..21478f749d 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/FeeSettings.h +++ b/include/xrpl/protocol_autogen/ledger_entries/FeeSettings.h @@ -294,7 +294,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfBaseFee (SoeOptional) diff --git a/include/xrpl/protocol_autogen/ledger_entries/LedgerHashes.h b/include/xrpl/protocol_autogen/ledger_entries/LedgerHashes.h index f1d3684b55..2c6747b07b 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/LedgerHashes.h +++ b/include/xrpl/protocol_autogen/ledger_entries/LedgerHashes.h @@ -139,7 +139,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfFirstLedgerSequence (SoeOptional) diff --git a/include/xrpl/protocol_autogen/ledger_entries/Loan.h b/include/xrpl/protocol_autogen/ledger_entries/Loan.h index 5d837736ec..a0abf9bd97 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Loan.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Loan.h @@ -616,7 +616,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfPreviousTxnID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/LoanBroker.h b/include/xrpl/protocol_autogen/ledger_entries/LoanBroker.h index 88f05e3433..281af2cfeb 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/LoanBroker.h +++ b/include/xrpl/protocol_autogen/ledger_entries/LoanBroker.h @@ -387,7 +387,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfPreviousTxnID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/MPToken.h b/include/xrpl/protocol_autogen/ledger_entries/MPToken.h index 379cfe53f5..874d779d09 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/MPToken.h +++ b/include/xrpl/protocol_autogen/ledger_entries/MPToken.h @@ -335,7 +335,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/MPTokenIssuance.h b/include/xrpl/protocol_autogen/ledger_entries/MPTokenIssuance.h index b6c77093ac..8518a0fe14 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/MPTokenIssuance.h +++ b/include/xrpl/protocol_autogen/ledger_entries/MPTokenIssuance.h @@ -420,7 +420,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfIssuer (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/NFTokenOffer.h b/include/xrpl/protocol_autogen/ledger_entries/NFTokenOffer.h index 072d3721f9..61aaccacdc 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/NFTokenOffer.h +++ b/include/xrpl/protocol_autogen/ledger_entries/NFTokenOffer.h @@ -217,7 +217,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfOwner (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/NFTokenPage.h b/include/xrpl/protocol_autogen/ledger_entries/NFTokenPage.h index 5e00cb1120..1aea6d6b01 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/NFTokenPage.h +++ b/include/xrpl/protocol_autogen/ledger_entries/NFTokenPage.h @@ -166,7 +166,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfPreviousPageMin (SoeOptional) diff --git a/include/xrpl/protocol_autogen/ledger_entries/NegativeUNL.h b/include/xrpl/protocol_autogen/ledger_entries/NegativeUNL.h index 7ca9729082..a35865202d 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/NegativeUNL.h +++ b/include/xrpl/protocol_autogen/ledger_entries/NegativeUNL.h @@ -199,7 +199,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfDisabledValidators (SoeOptional) diff --git a/include/xrpl/protocol_autogen/ledger_entries/Offer.h b/include/xrpl/protocol_autogen/ledger_entries/Offer.h index f51b54cfd2..e3539fc1fc 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Offer.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Offer.h @@ -268,7 +268,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/Oracle.h b/include/xrpl/protocol_autogen/ledger_entries/Oracle.h index 902032f94f..727e63ea84 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Oracle.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Oracle.h @@ -231,7 +231,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfOwner (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/PayChannel.h b/include/xrpl/protocol_autogen/ledger_entries/PayChannel.h index 61a4e2d044..4d00fbb7e5 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/PayChannel.h +++ b/include/xrpl/protocol_autogen/ledger_entries/PayChannel.h @@ -339,7 +339,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/PermissionedDomain.h b/include/xrpl/protocol_autogen/ledger_entries/PermissionedDomain.h index 638dda2420..e793e2d658 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/PermissionedDomain.h +++ b/include/xrpl/protocol_autogen/ledger_entries/PermissionedDomain.h @@ -157,7 +157,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfOwner (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/RippleState.h b/include/xrpl/protocol_autogen/ledger_entries/RippleState.h index e8debfe792..dda1b78e66 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/RippleState.h +++ b/include/xrpl/protocol_autogen/ledger_entries/RippleState.h @@ -243,6 +243,54 @@ public: { return this->sle_->isFieldPresent(sfHighQualityOut); } + + /** + * @brief Get sfHighSponsor (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getHighSponsor() const + { + if (hasHighSponsor()) + return this->sle_->at(sfHighSponsor); + return std::nullopt; + } + + /** + * @brief Check if sfHighSponsor is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasHighSponsor() const + { + return this->sle_->isFieldPresent(sfHighSponsor); + } + + /** + * @brief Get sfLowSponsor (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getLowSponsor() const + { + if (hasLowSponsor()) + return this->sle_->at(sfLowSponsor); + return std::nullopt; + } + + /** + * @brief Check if sfLowSponsor is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasLowSponsor() const + { + return this->sle_->isFieldPresent(sfLowSponsor); + } }; /** @@ -287,7 +335,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfBalance (SoeRequired) @@ -410,6 +460,28 @@ public: return *this; } + /** + * @brief Set sfHighSponsor (SoeOptional) + * @return Reference to this builder for method chaining. + */ + RippleStateBuilder& + setHighSponsor(std::decay_t const& value) + { + object_[sfHighSponsor] = value; + return *this; + } + + /** + * @brief Set sfLowSponsor (SoeOptional) + * @return Reference to this builder for method chaining. + */ + RippleStateBuilder& + setLowSponsor(std::decay_t const& value) + { + object_[sfLowSponsor] = value; + return *this; + } + /** * @brief Build and return the completed RippleState wrapper. * @param index The ledger entry index. diff --git a/include/xrpl/protocol_autogen/ledger_entries/SignerList.h b/include/xrpl/protocol_autogen/ledger_entries/SignerList.h index 443e5588f9..3aff8fa51b 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/SignerList.h +++ b/include/xrpl/protocol_autogen/ledger_entries/SignerList.h @@ -181,7 +181,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfOwner (SoeOptional) diff --git a/include/xrpl/protocol_autogen/ledger_entries/Sponsorship.h b/include/xrpl/protocol_autogen/ledger_entries/Sponsorship.h new file mode 100644 index 0000000000..065e655682 --- /dev/null +++ b/include/xrpl/protocol_autogen/ledger_entries/Sponsorship.h @@ -0,0 +1,346 @@ +// This file is auto-generated. Do not edit. +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl::ledger_entries { + +class SponsorshipBuilder; + +/** + * @brief Ledger Entry: Sponsorship + * + * Type: ltSPONSORSHIP (0x0090) + * RPC Name: sponsorship + * + * Immutable wrapper around SLE providing type-safe field access. + * Use SponsorshipBuilder to construct new ledger entries. + */ +class Sponsorship : public LedgerEntryBase +{ +public: + static constexpr LedgerEntryType entryType = ltSPONSORSHIP; + + /** + * @brief Construct a Sponsorship ledger entry wrapper from an existing SLE object. + * @throws std::runtime_error if the ledger entry type doesn't match. + */ + explicit Sponsorship(SLE::const_pointer sle) + : LedgerEntryBase(std::move(sle)) + { + // Verify ledger entry type + if (sle_->getType() != entryType) + { + throw std::runtime_error("Invalid ledger entry type for Sponsorship"); + } + } + + // Ledger entry-specific field getters + + /** + * @brief Get sfPreviousTxnID (SoeRequired) + * @return The field value. + */ + [[nodiscard]] + SF_UINT256::type::value_type + getPreviousTxnID() const + { + return this->sle_->at(sfPreviousTxnID); + } + + /** + * @brief Get sfPreviousTxnLgrSeq (SoeRequired) + * @return The field value. + */ + [[nodiscard]] + SF_UINT32::type::value_type + getPreviousTxnLgrSeq() const + { + return this->sle_->at(sfPreviousTxnLgrSeq); + } + + /** + * @brief Get sfOwner (SoeRequired) + * @return The field value. + */ + [[nodiscard]] + SF_ACCOUNT::type::value_type + getOwner() const + { + return this->sle_->at(sfOwner); + } + + /** + * @brief Get sfSponsee (SoeRequired) + * @return The field value. + */ + [[nodiscard]] + SF_ACCOUNT::type::value_type + getSponsee() const + { + return this->sle_->at(sfSponsee); + } + + /** + * @brief Get sfFeeAmount (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getFeeAmount() const + { + if (hasFeeAmount()) + return this->sle_->at(sfFeeAmount); + return std::nullopt; + } + + /** + * @brief Check if sfFeeAmount is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasFeeAmount() const + { + return this->sle_->isFieldPresent(sfFeeAmount); + } + + /** + * @brief Get sfMaxFee (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getMaxFee() const + { + if (hasMaxFee()) + return this->sle_->at(sfMaxFee); + return std::nullopt; + } + + /** + * @brief Check if sfMaxFee is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasMaxFee() const + { + return this->sle_->isFieldPresent(sfMaxFee); + } + + /** + * @brief Get sfRemainingOwnerCount (SoeDefault) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getRemainingOwnerCount() const + { + if (hasRemainingOwnerCount()) + return this->sle_->at(sfRemainingOwnerCount); + return std::nullopt; + } + + /** + * @brief Check if sfRemainingOwnerCount is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasRemainingOwnerCount() const + { + return this->sle_->isFieldPresent(sfRemainingOwnerCount); + } + + /** + * @brief Get sfOwnerNode (SoeRequired) + * @return The field value. + */ + [[nodiscard]] + SF_UINT64::type::value_type + getOwnerNode() const + { + return this->sle_->at(sfOwnerNode); + } + + /** + * @brief Get sfSponseeNode (SoeRequired) + * @return The field value. + */ + [[nodiscard]] + SF_UINT64::type::value_type + getSponseeNode() const + { + return this->sle_->at(sfSponseeNode); + } +}; + +/** + * @brief Builder for Sponsorship ledger entries. + * + * Provides a fluent interface for constructing ledger entries with method chaining. + * Uses STObject internally for flexible ledger entry construction. + * Inherits common field setters from LedgerEntryBuilderBase. + */ +class SponsorshipBuilder : public LedgerEntryBuilderBase +{ +public: + /** + * @brief Construct a new SponsorshipBuilder with required fields. + * @param previousTxnID The sfPreviousTxnID field value. + * @param previousTxnLgrSeq The sfPreviousTxnLgrSeq field value. + * @param owner The sfOwner field value. + * @param sponsee The sfSponsee field value. + * @param ownerNode The sfOwnerNode field value. + * @param sponseeNode The sfSponseeNode field value. + */ + SponsorshipBuilder(std::decay_t const& previousTxnID,std::decay_t const& previousTxnLgrSeq,std::decay_t const& owner,std::decay_t const& sponsee,std::decay_t const& ownerNode,std::decay_t const& sponseeNode) + : LedgerEntryBuilderBase(ltSPONSORSHIP) + { + setPreviousTxnID(previousTxnID); + setPreviousTxnLgrSeq(previousTxnLgrSeq); + setOwner(owner); + setSponsee(sponsee); + setOwnerNode(ownerNode); + setSponseeNode(sponseeNode); + } + + /** + * @brief Construct a SponsorshipBuilder from an existing SLE object. + * @param sle The existing ledger entry to copy from. + * @throws std::runtime_error if the ledger entry type doesn't match. + */ + SponsorshipBuilder(SLE::const_pointer sle) + { + if (sle->at(sfLedgerEntryType) != ltSPONSORSHIP) + { + throw std::runtime_error("Invalid ledger entry type for Sponsorship"); + } + object_ = *sle; + } + + /** + * @brief Ledger entry-specific field setters + */ + + /** + * @brief Set sfPreviousTxnID (SoeRequired) + * @return Reference to this builder for method chaining. + */ + SponsorshipBuilder& + setPreviousTxnID(std::decay_t const& value) + { + object_[sfPreviousTxnID] = value; + return *this; + } + + /** + * @brief Set sfPreviousTxnLgrSeq (SoeRequired) + * @return Reference to this builder for method chaining. + */ + SponsorshipBuilder& + setPreviousTxnLgrSeq(std::decay_t const& value) + { + object_[sfPreviousTxnLgrSeq] = value; + return *this; + } + + /** + * @brief Set sfOwner (SoeRequired) + * @return Reference to this builder for method chaining. + */ + SponsorshipBuilder& + setOwner(std::decay_t const& value) + { + object_[sfOwner] = value; + return *this; + } + + /** + * @brief Set sfSponsee (SoeRequired) + * @return Reference to this builder for method chaining. + */ + SponsorshipBuilder& + setSponsee(std::decay_t const& value) + { + object_[sfSponsee] = value; + return *this; + } + + /** + * @brief Set sfFeeAmount (SoeOptional) + * @return Reference to this builder for method chaining. + */ + SponsorshipBuilder& + setFeeAmount(std::decay_t const& value) + { + object_[sfFeeAmount] = value; + return *this; + } + + /** + * @brief Set sfMaxFee (SoeOptional) + * @return Reference to this builder for method chaining. + */ + SponsorshipBuilder& + setMaxFee(std::decay_t const& value) + { + object_[sfMaxFee] = value; + return *this; + } + + /** + * @brief Set sfRemainingOwnerCount (SoeDefault) + * @return Reference to this builder for method chaining. + */ + SponsorshipBuilder& + setRemainingOwnerCount(std::decay_t const& value) + { + object_[sfRemainingOwnerCount] = value; + return *this; + } + + /** + * @brief Set sfOwnerNode (SoeRequired) + * @return Reference to this builder for method chaining. + */ + SponsorshipBuilder& + setOwnerNode(std::decay_t const& value) + { + object_[sfOwnerNode] = value; + return *this; + } + + /** + * @brief Set sfSponseeNode (SoeRequired) + * @return Reference to this builder for method chaining. + */ + SponsorshipBuilder& + setSponseeNode(std::decay_t const& value) + { + object_[sfSponseeNode] = value; + return *this; + } + + /** + * @brief Build and return the completed Sponsorship wrapper. + * @param index The ledger entry index. + * @return The constructed ledger entry wrapper. + */ + Sponsorship + build(uint256 const& index) + { + return Sponsorship{std::make_shared(std::move(object_), index)}; + } +}; + +} // namespace xrpl::ledger_entries diff --git a/include/xrpl/protocol_autogen/ledger_entries/Ticket.h b/include/xrpl/protocol_autogen/ledger_entries/Ticket.h index 6fa5b57f6c..e1205bdb67 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Ticket.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Ticket.h @@ -143,7 +143,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/Vault.h b/include/xrpl/protocol_autogen/ledger_entries/Vault.h index d1aaeb4ed8..2bf92b4f5d 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Vault.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Vault.h @@ -339,7 +339,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfPreviousTxnID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/XChainOwnedClaimID.h b/include/xrpl/protocol_autogen/ledger_entries/XChainOwnedClaimID.h index 3f8058a4a1..bfd7a0a8b8 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/XChainOwnedClaimID.h +++ b/include/xrpl/protocol_autogen/ledger_entries/XChainOwnedClaimID.h @@ -196,7 +196,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/XChainOwnedCreateAccountClaimID.h b/include/xrpl/protocol_autogen/ledger_entries/XChainOwnedCreateAccountClaimID.h index e24009a4b7..4872d4063e 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/XChainOwnedCreateAccountClaimID.h +++ b/include/xrpl/protocol_autogen/ledger_entries/XChainOwnedCreateAccountClaimID.h @@ -170,7 +170,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/AMMBid.h b/include/xrpl/protocol_autogen/transactions/AMMBid.h index cd2792e810..30a2b6f2ab 100644 --- a/include/xrpl/protocol_autogen/transactions/AMMBid.h +++ b/include/xrpl/protocol_autogen/transactions/AMMBid.h @@ -190,7 +190,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfAsset (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/AMMClawback.h b/include/xrpl/protocol_autogen/transactions/AMMClawback.h index ccbd7d99e6..38aba892c4 100644 --- a/include/xrpl/protocol_autogen/transactions/AMMClawback.h +++ b/include/xrpl/protocol_autogen/transactions/AMMClawback.h @@ -154,7 +154,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfHolder (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/AMMCreate.h b/include/xrpl/protocol_autogen/transactions/AMMCreate.h index cc88428e7a..c6ccd4e860 100644 --- a/include/xrpl/protocol_autogen/transactions/AMMCreate.h +++ b/include/xrpl/protocol_autogen/transactions/AMMCreate.h @@ -127,7 +127,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfAmount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/AMMDelete.h b/include/xrpl/protocol_autogen/transactions/AMMDelete.h index 4cc0497c32..05899a46c8 100644 --- a/include/xrpl/protocol_autogen/transactions/AMMDelete.h +++ b/include/xrpl/protocol_autogen/transactions/AMMDelete.h @@ -114,7 +114,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfAsset (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/AMMDeposit.h b/include/xrpl/protocol_autogen/transactions/AMMDeposit.h index e01332c3e2..5416547dab 100644 --- a/include/xrpl/protocol_autogen/transactions/AMMDeposit.h +++ b/include/xrpl/protocol_autogen/transactions/AMMDeposit.h @@ -246,7 +246,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfAsset (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/AMMVote.h b/include/xrpl/protocol_autogen/transactions/AMMVote.h index b19d440c84..7dce3c252f 100644 --- a/include/xrpl/protocol_autogen/transactions/AMMVote.h +++ b/include/xrpl/protocol_autogen/transactions/AMMVote.h @@ -127,7 +127,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfAsset (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/AMMWithdraw.h b/include/xrpl/protocol_autogen/transactions/AMMWithdraw.h index 196f0faba2..81258f22d6 100644 --- a/include/xrpl/protocol_autogen/transactions/AMMWithdraw.h +++ b/include/xrpl/protocol_autogen/transactions/AMMWithdraw.h @@ -220,7 +220,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfAsset (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/AccountDelete.h b/include/xrpl/protocol_autogen/transactions/AccountDelete.h index c0346d9499..cf6e97bb63 100644 --- a/include/xrpl/protocol_autogen/transactions/AccountDelete.h +++ b/include/xrpl/protocol_autogen/transactions/AccountDelete.h @@ -151,7 +151,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfDestination (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/AccountSet.h b/include/xrpl/protocol_autogen/transactions/AccountSet.h index 7ddda45752..55c449e78e 100644 --- a/include/xrpl/protocol_autogen/transactions/AccountSet.h +++ b/include/xrpl/protocol_autogen/transactions/AccountSet.h @@ -346,7 +346,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfEmailHash (SoeOptional) diff --git a/include/xrpl/protocol_autogen/transactions/Batch.h b/include/xrpl/protocol_autogen/transactions/Batch.h index 00f553ada7..1a59d2b4c0 100644 --- a/include/xrpl/protocol_autogen/transactions/Batch.h +++ b/include/xrpl/protocol_autogen/transactions/Batch.h @@ -123,7 +123,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfRawTransactions (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/CheckCancel.h b/include/xrpl/protocol_autogen/transactions/CheckCancel.h index 4f7534278b..b75b717e3f 100644 --- a/include/xrpl/protocol_autogen/transactions/CheckCancel.h +++ b/include/xrpl/protocol_autogen/transactions/CheckCancel.h @@ -99,7 +99,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfCheckID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/CheckCash.h b/include/xrpl/protocol_autogen/transactions/CheckCash.h index a58a20c57e..c742a15154 100644 --- a/include/xrpl/protocol_autogen/transactions/CheckCash.h +++ b/include/xrpl/protocol_autogen/transactions/CheckCash.h @@ -153,7 +153,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfCheckID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/CheckCreate.h b/include/xrpl/protocol_autogen/transactions/CheckCreate.h index 17f985ac63..63e55f8604 100644 --- a/include/xrpl/protocol_autogen/transactions/CheckCreate.h +++ b/include/xrpl/protocol_autogen/transactions/CheckCreate.h @@ -191,7 +191,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfDestination (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/Clawback.h b/include/xrpl/protocol_autogen/transactions/Clawback.h index ecd7ebe7a2..9a3a7f9feb 100644 --- a/include/xrpl/protocol_autogen/transactions/Clawback.h +++ b/include/xrpl/protocol_autogen/transactions/Clawback.h @@ -126,7 +126,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfAmount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/ConfidentialMPTClawback.h b/include/xrpl/protocol_autogen/transactions/ConfidentialMPTClawback.h index 2b16590649..c80fc81dc5 100644 --- a/include/xrpl/protocol_autogen/transactions/ConfidentialMPTClawback.h +++ b/include/xrpl/protocol_autogen/transactions/ConfidentialMPTClawback.h @@ -138,7 +138,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfMPTokenIssuanceID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/ConfidentialMPTConvert.h b/include/xrpl/protocol_autogen/transactions/ConfidentialMPTConvert.h index f7a4cb601a..dec7f733c9 100644 --- a/include/xrpl/protocol_autogen/transactions/ConfidentialMPTConvert.h +++ b/include/xrpl/protocol_autogen/transactions/ConfidentialMPTConvert.h @@ -229,7 +229,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfMPTokenIssuanceID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/ConfidentialMPTConvertBack.h b/include/xrpl/protocol_autogen/transactions/ConfidentialMPTConvertBack.h index 68bf326645..53a8e64125 100644 --- a/include/xrpl/protocol_autogen/transactions/ConfidentialMPTConvertBack.h +++ b/include/xrpl/protocol_autogen/transactions/ConfidentialMPTConvertBack.h @@ -203,7 +203,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfMPTokenIssuanceID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/ConfidentialMPTMergeInbox.h b/include/xrpl/protocol_autogen/transactions/ConfidentialMPTMergeInbox.h index bb932080d8..848da42a41 100644 --- a/include/xrpl/protocol_autogen/transactions/ConfidentialMPTMergeInbox.h +++ b/include/xrpl/protocol_autogen/transactions/ConfidentialMPTMergeInbox.h @@ -99,7 +99,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfMPTokenIssuanceID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/ConfidentialMPTSend.h b/include/xrpl/protocol_autogen/transactions/ConfidentialMPTSend.h index 2d8a77d56f..806a2586e9 100644 --- a/include/xrpl/protocol_autogen/transactions/ConfidentialMPTSend.h +++ b/include/xrpl/protocol_autogen/transactions/ConfidentialMPTSend.h @@ -268,7 +268,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfMPTokenIssuanceID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/CredentialAccept.h b/include/xrpl/protocol_autogen/transactions/CredentialAccept.h index 152c18ea09..f2ab546320 100644 --- a/include/xrpl/protocol_autogen/transactions/CredentialAccept.h +++ b/include/xrpl/protocol_autogen/transactions/CredentialAccept.h @@ -112,7 +112,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfIssuer (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/CredentialCreate.h b/include/xrpl/protocol_autogen/transactions/CredentialCreate.h index d7e056f590..6cf09c852b 100644 --- a/include/xrpl/protocol_autogen/transactions/CredentialCreate.h +++ b/include/xrpl/protocol_autogen/transactions/CredentialCreate.h @@ -164,7 +164,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfSubject (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/CredentialDelete.h b/include/xrpl/protocol_autogen/transactions/CredentialDelete.h index 512f230a26..24a2bfa62a 100644 --- a/include/xrpl/protocol_autogen/transactions/CredentialDelete.h +++ b/include/xrpl/protocol_autogen/transactions/CredentialDelete.h @@ -151,7 +151,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfSubject (SoeOptional) diff --git a/include/xrpl/protocol_autogen/transactions/DIDDelete.h b/include/xrpl/protocol_autogen/transactions/DIDDelete.h index 5f90821bfd..304287883d 100644 --- a/include/xrpl/protocol_autogen/transactions/DIDDelete.h +++ b/include/xrpl/protocol_autogen/transactions/DIDDelete.h @@ -86,7 +86,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Build and return the DIDDelete wrapper. diff --git a/include/xrpl/protocol_autogen/transactions/DIDSet.h b/include/xrpl/protocol_autogen/transactions/DIDSet.h index 27242cda71..67e5ba23c5 100644 --- a/include/xrpl/protocol_autogen/transactions/DIDSet.h +++ b/include/xrpl/protocol_autogen/transactions/DIDSet.h @@ -164,7 +164,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfDIDDocument (SoeOptional) diff --git a/include/xrpl/protocol_autogen/transactions/DelegateSet.h b/include/xrpl/protocol_autogen/transactions/DelegateSet.h index 1f454ff4a1..592a778952 100644 --- a/include/xrpl/protocol_autogen/transactions/DelegateSet.h +++ b/include/xrpl/protocol_autogen/transactions/DelegateSet.h @@ -112,7 +112,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfAuthorize (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/DepositPreauth.h b/include/xrpl/protocol_autogen/transactions/DepositPreauth.h index d303ffbeff..b5d575aac5 100644 --- a/include/xrpl/protocol_autogen/transactions/DepositPreauth.h +++ b/include/xrpl/protocol_autogen/transactions/DepositPreauth.h @@ -186,7 +186,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfAuthorize (SoeOptional) diff --git a/include/xrpl/protocol_autogen/transactions/EnableAmendment.h b/include/xrpl/protocol_autogen/transactions/EnableAmendment.h index b397c5df87..e811ca16df 100644 --- a/include/xrpl/protocol_autogen/transactions/EnableAmendment.h +++ b/include/xrpl/protocol_autogen/transactions/EnableAmendment.h @@ -112,7 +112,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfLedgerSequence (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/EscrowCancel.h b/include/xrpl/protocol_autogen/transactions/EscrowCancel.h index 4da943c351..e7e49eca0d 100644 --- a/include/xrpl/protocol_autogen/transactions/EscrowCancel.h +++ b/include/xrpl/protocol_autogen/transactions/EscrowCancel.h @@ -112,7 +112,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfOwner (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/EscrowCreate.h b/include/xrpl/protocol_autogen/transactions/EscrowCreate.h index 35775c31ae..b994e4ec07 100644 --- a/include/xrpl/protocol_autogen/transactions/EscrowCreate.h +++ b/include/xrpl/protocol_autogen/transactions/EscrowCreate.h @@ -217,7 +217,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfDestination (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/EscrowFinish.h b/include/xrpl/protocol_autogen/transactions/EscrowFinish.h index f6ca73d209..2476def5c2 100644 --- a/include/xrpl/protocol_autogen/transactions/EscrowFinish.h +++ b/include/xrpl/protocol_autogen/transactions/EscrowFinish.h @@ -190,7 +190,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfOwner (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/LedgerStateFix.h b/include/xrpl/protocol_autogen/transactions/LedgerStateFix.h index 52723ad5eb..af86dea0b0 100644 --- a/include/xrpl/protocol_autogen/transactions/LedgerStateFix.h +++ b/include/xrpl/protocol_autogen/transactions/LedgerStateFix.h @@ -151,7 +151,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfLedgerFixType (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/LoanBrokerCoverClawback.h b/include/xrpl/protocol_autogen/transactions/LoanBrokerCoverClawback.h index 4842381362..875e0a4c5e 100644 --- a/include/xrpl/protocol_autogen/transactions/LoanBrokerCoverClawback.h +++ b/include/xrpl/protocol_autogen/transactions/LoanBrokerCoverClawback.h @@ -139,7 +139,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfLoanBrokerID (SoeOptional) diff --git a/include/xrpl/protocol_autogen/transactions/LoanBrokerCoverDeposit.h b/include/xrpl/protocol_autogen/transactions/LoanBrokerCoverDeposit.h index 98cebdccb2..38cc113844 100644 --- a/include/xrpl/protocol_autogen/transactions/LoanBrokerCoverDeposit.h +++ b/include/xrpl/protocol_autogen/transactions/LoanBrokerCoverDeposit.h @@ -113,7 +113,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfLoanBrokerID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/LoanBrokerCoverWithdraw.h b/include/xrpl/protocol_autogen/transactions/LoanBrokerCoverWithdraw.h index e734c6802a..56a93acbb4 100644 --- a/include/xrpl/protocol_autogen/transactions/LoanBrokerCoverWithdraw.h +++ b/include/xrpl/protocol_autogen/transactions/LoanBrokerCoverWithdraw.h @@ -165,7 +165,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfLoanBrokerID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/LoanBrokerDelete.h b/include/xrpl/protocol_autogen/transactions/LoanBrokerDelete.h index 55c0a12381..29b3a787fd 100644 --- a/include/xrpl/protocol_autogen/transactions/LoanBrokerDelete.h +++ b/include/xrpl/protocol_autogen/transactions/LoanBrokerDelete.h @@ -99,7 +99,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfLoanBrokerID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/LoanBrokerSet.h b/include/xrpl/protocol_autogen/transactions/LoanBrokerSet.h index 854022242d..41c87c281d 100644 --- a/include/xrpl/protocol_autogen/transactions/LoanBrokerSet.h +++ b/include/xrpl/protocol_autogen/transactions/LoanBrokerSet.h @@ -255,7 +255,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfVaultID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/LoanDelete.h b/include/xrpl/protocol_autogen/transactions/LoanDelete.h index 70c9e50097..8ed537b37a 100644 --- a/include/xrpl/protocol_autogen/transactions/LoanDelete.h +++ b/include/xrpl/protocol_autogen/transactions/LoanDelete.h @@ -99,7 +99,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfLoanID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/LoanManage.h b/include/xrpl/protocol_autogen/transactions/LoanManage.h index f11782c4e1..5eb95d21b1 100644 --- a/include/xrpl/protocol_autogen/transactions/LoanManage.h +++ b/include/xrpl/protocol_autogen/transactions/LoanManage.h @@ -99,7 +99,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfLoanID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/LoanPay.h b/include/xrpl/protocol_autogen/transactions/LoanPay.h index 4012225e17..8e1faeb981 100644 --- a/include/xrpl/protocol_autogen/transactions/LoanPay.h +++ b/include/xrpl/protocol_autogen/transactions/LoanPay.h @@ -113,7 +113,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfLoanID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/LoanSet.h b/include/xrpl/protocol_autogen/transactions/LoanSet.h index 3fa3c905c2..2cadebd02e 100644 --- a/include/xrpl/protocol_autogen/transactions/LoanSet.h +++ b/include/xrpl/protocol_autogen/transactions/LoanSet.h @@ -500,7 +500,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfLoanBrokerID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/MPTokenAuthorize.h b/include/xrpl/protocol_autogen/transactions/MPTokenAuthorize.h index 90440c41f9..2fb93eaf35 100644 --- a/include/xrpl/protocol_autogen/transactions/MPTokenAuthorize.h +++ b/include/xrpl/protocol_autogen/transactions/MPTokenAuthorize.h @@ -125,7 +125,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfMPTokenIssuanceID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/MPTokenIssuanceCreate.h b/include/xrpl/protocol_autogen/transactions/MPTokenIssuanceCreate.h index d723a3041c..e6fece8354 100644 --- a/include/xrpl/protocol_autogen/transactions/MPTokenIssuanceCreate.h +++ b/include/xrpl/protocol_autogen/transactions/MPTokenIssuanceCreate.h @@ -242,7 +242,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfAssetScale (SoeOptional) diff --git a/include/xrpl/protocol_autogen/transactions/MPTokenIssuanceDestroy.h b/include/xrpl/protocol_autogen/transactions/MPTokenIssuanceDestroy.h index 19c4530792..cbcd206097 100644 --- a/include/xrpl/protocol_autogen/transactions/MPTokenIssuanceDestroy.h +++ b/include/xrpl/protocol_autogen/transactions/MPTokenIssuanceDestroy.h @@ -99,7 +99,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfMPTokenIssuanceID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/MPTokenIssuanceSet.h b/include/xrpl/protocol_autogen/transactions/MPTokenIssuanceSet.h index 8099af1148..803868c640 100644 --- a/include/xrpl/protocol_autogen/transactions/MPTokenIssuanceSet.h +++ b/include/xrpl/protocol_autogen/transactions/MPTokenIssuanceSet.h @@ -281,7 +281,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfMPTokenIssuanceID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/NFTokenAcceptOffer.h b/include/xrpl/protocol_autogen/transactions/NFTokenAcceptOffer.h index 7627437a52..325d2d7fbd 100644 --- a/include/xrpl/protocol_autogen/transactions/NFTokenAcceptOffer.h +++ b/include/xrpl/protocol_autogen/transactions/NFTokenAcceptOffer.h @@ -164,7 +164,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfNFTokenBuyOffer (SoeOptional) diff --git a/include/xrpl/protocol_autogen/transactions/NFTokenBurn.h b/include/xrpl/protocol_autogen/transactions/NFTokenBurn.h index 9f981b1ec4..ec423ea468 100644 --- a/include/xrpl/protocol_autogen/transactions/NFTokenBurn.h +++ b/include/xrpl/protocol_autogen/transactions/NFTokenBurn.h @@ -125,7 +125,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfNFTokenID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/NFTokenCancelOffer.h b/include/xrpl/protocol_autogen/transactions/NFTokenCancelOffer.h index 2eca27fc8d..4c4fb1dc65 100644 --- a/include/xrpl/protocol_autogen/transactions/NFTokenCancelOffer.h +++ b/include/xrpl/protocol_autogen/transactions/NFTokenCancelOffer.h @@ -99,7 +99,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfNFTokenOffers (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/NFTokenCreateOffer.h b/include/xrpl/protocol_autogen/transactions/NFTokenCreateOffer.h index a5c6e226d9..a535a578e0 100644 --- a/include/xrpl/protocol_autogen/transactions/NFTokenCreateOffer.h +++ b/include/xrpl/protocol_autogen/transactions/NFTokenCreateOffer.h @@ -190,7 +190,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfNFTokenID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/NFTokenMint.h b/include/xrpl/protocol_autogen/transactions/NFTokenMint.h index ad68f1a18b..5af41eb3dd 100644 --- a/include/xrpl/protocol_autogen/transactions/NFTokenMint.h +++ b/include/xrpl/protocol_autogen/transactions/NFTokenMint.h @@ -255,7 +255,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfNFTokenTaxon (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/NFTokenModify.h b/include/xrpl/protocol_autogen/transactions/NFTokenModify.h index 277146ce78..9b9701fed6 100644 --- a/include/xrpl/protocol_autogen/transactions/NFTokenModify.h +++ b/include/xrpl/protocol_autogen/transactions/NFTokenModify.h @@ -151,7 +151,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfNFTokenID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/OfferCancel.h b/include/xrpl/protocol_autogen/transactions/OfferCancel.h index 833924f683..5e6010e0dd 100644 --- a/include/xrpl/protocol_autogen/transactions/OfferCancel.h +++ b/include/xrpl/protocol_autogen/transactions/OfferCancel.h @@ -99,7 +99,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfOfferSequence (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/OfferCreate.h b/include/xrpl/protocol_autogen/transactions/OfferCreate.h index 13e868643f..ffc1216297 100644 --- a/include/xrpl/protocol_autogen/transactions/OfferCreate.h +++ b/include/xrpl/protocol_autogen/transactions/OfferCreate.h @@ -192,7 +192,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfTakerPays (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/OracleDelete.h b/include/xrpl/protocol_autogen/transactions/OracleDelete.h index d6d46d1d7a..ebdc8fb7e9 100644 --- a/include/xrpl/protocol_autogen/transactions/OracleDelete.h +++ b/include/xrpl/protocol_autogen/transactions/OracleDelete.h @@ -99,7 +99,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfOracleDocumentID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/OracleSet.h b/include/xrpl/protocol_autogen/transactions/OracleSet.h index 1d295b4cc2..0ec6d5cad0 100644 --- a/include/xrpl/protocol_autogen/transactions/OracleSet.h +++ b/include/xrpl/protocol_autogen/transactions/OracleSet.h @@ -203,7 +203,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfOracleDocumentID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/Payment.h b/include/xrpl/protocol_autogen/transactions/Payment.h index b2e82cd6af..389900bf12 100644 --- a/include/xrpl/protocol_autogen/transactions/Payment.h +++ b/include/xrpl/protocol_autogen/transactions/Payment.h @@ -295,7 +295,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfDestination (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/PaymentChannelClaim.h b/include/xrpl/protocol_autogen/transactions/PaymentChannelClaim.h index 1db3057366..4c567b13f4 100644 --- a/include/xrpl/protocol_autogen/transactions/PaymentChannelClaim.h +++ b/include/xrpl/protocol_autogen/transactions/PaymentChannelClaim.h @@ -229,7 +229,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfChannel (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/PaymentChannelCreate.h b/include/xrpl/protocol_autogen/transactions/PaymentChannelCreate.h index cf0cbae2aa..0a513d575a 100644 --- a/include/xrpl/protocol_autogen/transactions/PaymentChannelCreate.h +++ b/include/xrpl/protocol_autogen/transactions/PaymentChannelCreate.h @@ -190,7 +190,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfDestination (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/PaymentChannelFund.h b/include/xrpl/protocol_autogen/transactions/PaymentChannelFund.h index 3612fcbf48..51210dd796 100644 --- a/include/xrpl/protocol_autogen/transactions/PaymentChannelFund.h +++ b/include/xrpl/protocol_autogen/transactions/PaymentChannelFund.h @@ -138,7 +138,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfChannel (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/PermissionedDomainDelete.h b/include/xrpl/protocol_autogen/transactions/PermissionedDomainDelete.h index e08ff1ed5a..3db921776c 100644 --- a/include/xrpl/protocol_autogen/transactions/PermissionedDomainDelete.h +++ b/include/xrpl/protocol_autogen/transactions/PermissionedDomainDelete.h @@ -99,7 +99,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfDomainID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/PermissionedDomainSet.h b/include/xrpl/protocol_autogen/transactions/PermissionedDomainSet.h index 68f1cfebb2..3e352cad76 100644 --- a/include/xrpl/protocol_autogen/transactions/PermissionedDomainSet.h +++ b/include/xrpl/protocol_autogen/transactions/PermissionedDomainSet.h @@ -125,7 +125,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfDomainID (SoeOptional) diff --git a/include/xrpl/protocol_autogen/transactions/SetFee.h b/include/xrpl/protocol_autogen/transactions/SetFee.h index bc5fc0e603..177f39199b 100644 --- a/include/xrpl/protocol_autogen/transactions/SetFee.h +++ b/include/xrpl/protocol_autogen/transactions/SetFee.h @@ -294,7 +294,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfLedgerSequence (SoeOptional) diff --git a/include/xrpl/protocol_autogen/transactions/SetRegularKey.h b/include/xrpl/protocol_autogen/transactions/SetRegularKey.h index 1eca1bef25..a943bb0279 100644 --- a/include/xrpl/protocol_autogen/transactions/SetRegularKey.h +++ b/include/xrpl/protocol_autogen/transactions/SetRegularKey.h @@ -112,7 +112,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfRegularKey (SoeOptional) diff --git a/include/xrpl/protocol_autogen/transactions/SignerListSet.h b/include/xrpl/protocol_autogen/transactions/SignerListSet.h index c711864e95..6e9d0e41ba 100644 --- a/include/xrpl/protocol_autogen/transactions/SignerListSet.h +++ b/include/xrpl/protocol_autogen/transactions/SignerListSet.h @@ -123,7 +123,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfSignerQuorum (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/SponsorshipSet.h b/include/xrpl/protocol_autogen/transactions/SponsorshipSet.h new file mode 100644 index 0000000000..0124da5e58 --- /dev/null +++ b/include/xrpl/protocol_autogen/transactions/SponsorshipSet.h @@ -0,0 +1,292 @@ +// This file is auto-generated. Do not edit. +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl::transactions { + +class SponsorshipSetBuilder; + +/** + * @brief Transaction: SponsorshipSet + * + * Type: ttSPONSORSHIP_SET (91) + * Delegable: Delegation::Delegable + * Amendment: featureSponsor + * Privileges: NoPriv + * + * Immutable wrapper around STTx providing type-safe field access. + * Use SponsorshipSetBuilder to construct new transactions. + */ +class SponsorshipSet : public TransactionBase +{ +public: + static constexpr xrpl::TxType txType = ttSPONSORSHIP_SET; + + /** + * @brief Construct a SponsorshipSet transaction wrapper from an existing STTx object. + * @throws std::runtime_error if the transaction type doesn't match. + */ + explicit SponsorshipSet(std::shared_ptr tx) + : TransactionBase(std::move(tx)) + { + // Verify transaction type + if (tx_->getTxnType() != txType) + { + throw std::runtime_error("Invalid transaction type for SponsorshipSet"); + } + } + + // Transaction-specific field getters + + /** + * @brief Get sfCounterpartySponsor (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getCounterpartySponsor() const + { + if (hasCounterpartySponsor()) + { + return this->tx_->at(sfCounterpartySponsor); + } + return std::nullopt; + } + + /** + * @brief Check if sfCounterpartySponsor is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasCounterpartySponsor() const + { + return this->tx_->isFieldPresent(sfCounterpartySponsor); + } + + /** + * @brief Get sfSponsee (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getSponsee() const + { + if (hasSponsee()) + { + return this->tx_->at(sfSponsee); + } + return std::nullopt; + } + + /** + * @brief Check if sfSponsee is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasSponsee() const + { + return this->tx_->isFieldPresent(sfSponsee); + } + + /** + * @brief Get sfFeeAmount (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getFeeAmount() const + { + if (hasFeeAmount()) + { + return this->tx_->at(sfFeeAmount); + } + return std::nullopt; + } + + /** + * @brief Check if sfFeeAmount is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasFeeAmount() const + { + return this->tx_->isFieldPresent(sfFeeAmount); + } + + /** + * @brief Get sfMaxFee (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getMaxFee() const + { + if (hasMaxFee()) + { + return this->tx_->at(sfMaxFee); + } + return std::nullopt; + } + + /** + * @brief Check if sfMaxFee is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasMaxFee() const + { + return this->tx_->isFieldPresent(sfMaxFee); + } + + /** + * @brief Get sfRemainingOwnerCount (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getRemainingOwnerCount() const + { + if (hasRemainingOwnerCount()) + { + return this->tx_->at(sfRemainingOwnerCount); + } + return std::nullopt; + } + + /** + * @brief Check if sfRemainingOwnerCount is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasRemainingOwnerCount() const + { + return this->tx_->isFieldPresent(sfRemainingOwnerCount); + } +}; + +/** + * @brief Builder for SponsorshipSet transactions. + * + * Provides a fluent interface for constructing transactions with method chaining. + * Uses STObject internally for flexible transaction construction. + * Inherits common field setters from TransactionBuilderBase. + */ +class SponsorshipSetBuilder : public TransactionBuilderBase +{ +public: + /** + * @brief Construct a new SponsorshipSetBuilder with required fields. + * @param account The account initiating the transaction. + * @param sequence Optional sequence number for the transaction. + * @param fee Optional fee for the transaction. + */ + SponsorshipSetBuilder(SF_ACCOUNT::type::value_type account, + std::optional sequence = std::nullopt, + std::optional fee = std::nullopt +) + : TransactionBuilderBase(ttSPONSORSHIP_SET, account, sequence, fee) + { + } + + /** + * @brief Construct a SponsorshipSetBuilder from an existing STTx object. + * @param tx The existing transaction to copy from. + * @throws std::runtime_error if the transaction type doesn't match. + */ + SponsorshipSetBuilder(std::shared_ptr tx) + { + if (tx->getTxnType() != ttSPONSORSHIP_SET) + { + throw std::runtime_error("Invalid transaction type for SponsorshipSetBuilder"); + } + object_ = *tx; + } + + /** + * @brief Transaction-specific field setters + */ + + /** + * @brief Set sfCounterpartySponsor (SoeOptional) + * @return Reference to this builder for method chaining. + */ + SponsorshipSetBuilder& + setCounterpartySponsor(std::decay_t const& value) + { + object_[sfCounterpartySponsor] = value; + return *this; + } + + /** + * @brief Set sfSponsee (SoeOptional) + * @return Reference to this builder for method chaining. + */ + SponsorshipSetBuilder& + setSponsee(std::decay_t const& value) + { + object_[sfSponsee] = value; + return *this; + } + + /** + * @brief Set sfFeeAmount (SoeOptional) + * @return Reference to this builder for method chaining. + */ + SponsorshipSetBuilder& + setFeeAmount(std::decay_t const& value) + { + object_[sfFeeAmount] = value; + return *this; + } + + /** + * @brief Set sfMaxFee (SoeOptional) + * @return Reference to this builder for method chaining. + */ + SponsorshipSetBuilder& + setMaxFee(std::decay_t const& value) + { + object_[sfMaxFee] = value; + return *this; + } + + /** + * @brief Set sfRemainingOwnerCount (SoeOptional) + * @return Reference to this builder for method chaining. + */ + SponsorshipSetBuilder& + setRemainingOwnerCount(std::decay_t const& value) + { + object_[sfRemainingOwnerCount] = value; + return *this; + } + + /** + * @brief Build and return the SponsorshipSet wrapper. + * @param publicKey The public key for signing. + * @param secretKey The secret key for signing. + * @return The constructed transaction wrapper. + */ + SponsorshipSet + build(PublicKey const& publicKey, SecretKey const& secretKey) + { + sign(publicKey, secretKey); + return SponsorshipSet{std::make_shared(std::move(object_))}; + } +}; + +} // namespace xrpl::transactions diff --git a/include/xrpl/protocol_autogen/transactions/SponsorshipTransfer.h b/include/xrpl/protocol_autogen/transactions/SponsorshipTransfer.h new file mode 100644 index 0000000000..ab26e887e3 --- /dev/null +++ b/include/xrpl/protocol_autogen/transactions/SponsorshipTransfer.h @@ -0,0 +1,181 @@ +// This file is auto-generated. Do not edit. +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl::transactions { + +class SponsorshipTransferBuilder; + +/** + * @brief Transaction: SponsorshipTransfer + * + * Type: ttSPONSORSHIP_TRANSFER (90) + * Delegable: Delegation::NotDelegable + * Amendment: featureSponsor + * Privileges: NoPriv + * + * Immutable wrapper around STTx providing type-safe field access. + * Use SponsorshipTransferBuilder to construct new transactions. + */ +class SponsorshipTransfer : public TransactionBase +{ +public: + static constexpr xrpl::TxType txType = ttSPONSORSHIP_TRANSFER; + + /** + * @brief Construct a SponsorshipTransfer transaction wrapper from an existing STTx object. + * @throws std::runtime_error if the transaction type doesn't match. + */ + explicit SponsorshipTransfer(std::shared_ptr tx) + : TransactionBase(std::move(tx)) + { + // Verify transaction type + if (tx_->getTxnType() != txType) + { + throw std::runtime_error("Invalid transaction type for SponsorshipTransfer"); + } + } + + // Transaction-specific field getters + + /** + * @brief Get sfObjectID (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getObjectID() const + { + if (hasObjectID()) + { + return this->tx_->at(sfObjectID); + } + return std::nullopt; + } + + /** + * @brief Check if sfObjectID is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasObjectID() const + { + return this->tx_->isFieldPresent(sfObjectID); + } + + /** + * @brief Get sfSponsee (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getSponsee() const + { + if (hasSponsee()) + { + return this->tx_->at(sfSponsee); + } + return std::nullopt; + } + + /** + * @brief Check if sfSponsee is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasSponsee() const + { + return this->tx_->isFieldPresent(sfSponsee); + } +}; + +/** + * @brief Builder for SponsorshipTransfer transactions. + * + * Provides a fluent interface for constructing transactions with method chaining. + * Uses STObject internally for flexible transaction construction. + * Inherits common field setters from TransactionBuilderBase. + */ +class SponsorshipTransferBuilder : public TransactionBuilderBase +{ +public: + /** + * @brief Construct a new SponsorshipTransferBuilder with required fields. + * @param account The account initiating the transaction. + * @param sequence Optional sequence number for the transaction. + * @param fee Optional fee for the transaction. + */ + SponsorshipTransferBuilder(SF_ACCOUNT::type::value_type account, + std::optional sequence = std::nullopt, + std::optional fee = std::nullopt +) + : TransactionBuilderBase(ttSPONSORSHIP_TRANSFER, account, sequence, fee) + { + } + + /** + * @brief Construct a SponsorshipTransferBuilder from an existing STTx object. + * @param tx The existing transaction to copy from. + * @throws std::runtime_error if the transaction type doesn't match. + */ + SponsorshipTransferBuilder(std::shared_ptr tx) + { + if (tx->getTxnType() != ttSPONSORSHIP_TRANSFER) + { + throw std::runtime_error("Invalid transaction type for SponsorshipTransferBuilder"); + } + object_ = *tx; + } + + /** + * @brief Transaction-specific field setters + */ + + /** + * @brief Set sfObjectID (SoeOptional) + * @return Reference to this builder for method chaining. + */ + SponsorshipTransferBuilder& + setObjectID(std::decay_t const& value) + { + object_[sfObjectID] = value; + return *this; + } + + /** + * @brief Set sfSponsee (SoeOptional) + * @return Reference to this builder for method chaining. + */ + SponsorshipTransferBuilder& + setSponsee(std::decay_t const& value) + { + object_[sfSponsee] = value; + return *this; + } + + /** + * @brief Build and return the SponsorshipTransfer wrapper. + * @param publicKey The public key for signing. + * @param secretKey The secret key for signing. + * @return The constructed transaction wrapper. + */ + SponsorshipTransfer + build(PublicKey const& publicKey, SecretKey const& secretKey) + { + sign(publicKey, secretKey); + return SponsorshipTransfer{std::make_shared(std::move(object_))}; + } +}; + +} // namespace xrpl::transactions diff --git a/include/xrpl/protocol_autogen/transactions/TicketCreate.h b/include/xrpl/protocol_autogen/transactions/TicketCreate.h index 0b4206152e..0d8670a76a 100644 --- a/include/xrpl/protocol_autogen/transactions/TicketCreate.h +++ b/include/xrpl/protocol_autogen/transactions/TicketCreate.h @@ -99,7 +99,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfTicketCount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/TrustSet.h b/include/xrpl/protocol_autogen/transactions/TrustSet.h index 30d537492d..22891b94ec 100644 --- a/include/xrpl/protocol_autogen/transactions/TrustSet.h +++ b/include/xrpl/protocol_autogen/transactions/TrustSet.h @@ -164,7 +164,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfLimitAmount (SoeOptional) diff --git a/include/xrpl/protocol_autogen/transactions/UNLModify.h b/include/xrpl/protocol_autogen/transactions/UNLModify.h index a8556ca699..6569e4bf7d 100644 --- a/include/xrpl/protocol_autogen/transactions/UNLModify.h +++ b/include/xrpl/protocol_autogen/transactions/UNLModify.h @@ -125,7 +125,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfUNLModifyDisabling (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/VaultClawback.h b/include/xrpl/protocol_autogen/transactions/VaultClawback.h index 8ec1d359dc..270ccc94bb 100644 --- a/include/xrpl/protocol_autogen/transactions/VaultClawback.h +++ b/include/xrpl/protocol_autogen/transactions/VaultClawback.h @@ -139,7 +139,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfVaultID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/VaultCreate.h b/include/xrpl/protocol_autogen/transactions/VaultCreate.h index c8efa83cbf..b7e1527754 100644 --- a/include/xrpl/protocol_autogen/transactions/VaultCreate.h +++ b/include/xrpl/protocol_autogen/transactions/VaultCreate.h @@ -256,7 +256,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfAsset (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/VaultDelete.h b/include/xrpl/protocol_autogen/transactions/VaultDelete.h index b4c08ae229..67cc32f543 100644 --- a/include/xrpl/protocol_autogen/transactions/VaultDelete.h +++ b/include/xrpl/protocol_autogen/transactions/VaultDelete.h @@ -125,7 +125,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfVaultID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/VaultDeposit.h b/include/xrpl/protocol_autogen/transactions/VaultDeposit.h index 01f707e07f..5bb5362114 100644 --- a/include/xrpl/protocol_autogen/transactions/VaultDeposit.h +++ b/include/xrpl/protocol_autogen/transactions/VaultDeposit.h @@ -113,7 +113,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfVaultID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/VaultSet.h b/include/xrpl/protocol_autogen/transactions/VaultSet.h index 5bdc6a7a95..14df70f13b 100644 --- a/include/xrpl/protocol_autogen/transactions/VaultSet.h +++ b/include/xrpl/protocol_autogen/transactions/VaultSet.h @@ -177,7 +177,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfVaultID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/VaultWithdraw.h b/include/xrpl/protocol_autogen/transactions/VaultWithdraw.h index 75b5b90035..3211524e1f 100644 --- a/include/xrpl/protocol_autogen/transactions/VaultWithdraw.h +++ b/include/xrpl/protocol_autogen/transactions/VaultWithdraw.h @@ -165,7 +165,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfVaultID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/XChainAccountCreateCommit.h b/include/xrpl/protocol_autogen/transactions/XChainAccountCreateCommit.h index 36d8170af1..b8d551c5e1 100644 --- a/include/xrpl/protocol_autogen/transactions/XChainAccountCreateCommit.h +++ b/include/xrpl/protocol_autogen/transactions/XChainAccountCreateCommit.h @@ -138,7 +138,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfXChainBridge (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/XChainAddAccountCreateAttestation.h b/include/xrpl/protocol_autogen/transactions/XChainAddAccountCreateAttestation.h index a04eec1505..22b57803dc 100644 --- a/include/xrpl/protocol_autogen/transactions/XChainAddAccountCreateAttestation.h +++ b/include/xrpl/protocol_autogen/transactions/XChainAddAccountCreateAttestation.h @@ -229,7 +229,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfXChainBridge (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/XChainAddClaimAttestation.h b/include/xrpl/protocol_autogen/transactions/XChainAddClaimAttestation.h index 1896f11681..5e80c05aae 100644 --- a/include/xrpl/protocol_autogen/transactions/XChainAddClaimAttestation.h +++ b/include/xrpl/protocol_autogen/transactions/XChainAddClaimAttestation.h @@ -229,7 +229,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfXChainBridge (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/XChainClaim.h b/include/xrpl/protocol_autogen/transactions/XChainClaim.h index f5b680049e..ec403b5eb8 100644 --- a/include/xrpl/protocol_autogen/transactions/XChainClaim.h +++ b/include/xrpl/protocol_autogen/transactions/XChainClaim.h @@ -164,7 +164,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfXChainBridge (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/XChainCommit.h b/include/xrpl/protocol_autogen/transactions/XChainCommit.h index a9ce7d1d08..48b2263645 100644 --- a/include/xrpl/protocol_autogen/transactions/XChainCommit.h +++ b/include/xrpl/protocol_autogen/transactions/XChainCommit.h @@ -151,7 +151,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfXChainBridge (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/XChainCreateBridge.h b/include/xrpl/protocol_autogen/transactions/XChainCreateBridge.h index ae5be4108f..9614b0bd88 100644 --- a/include/xrpl/protocol_autogen/transactions/XChainCreateBridge.h +++ b/include/xrpl/protocol_autogen/transactions/XChainCreateBridge.h @@ -138,7 +138,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfXChainBridge (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/XChainCreateClaimID.h b/include/xrpl/protocol_autogen/transactions/XChainCreateClaimID.h index 7c1beb20c7..d17759619f 100644 --- a/include/xrpl/protocol_autogen/transactions/XChainCreateClaimID.h +++ b/include/xrpl/protocol_autogen/transactions/XChainCreateClaimID.h @@ -125,7 +125,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfXChainBridge (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/XChainModifyBridge.h b/include/xrpl/protocol_autogen/transactions/XChainModifyBridge.h index 30558ab88c..e79c9139ce 100644 --- a/include/xrpl/protocol_autogen/transactions/XChainModifyBridge.h +++ b/include/xrpl/protocol_autogen/transactions/XChainModifyBridge.h @@ -151,7 +151,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfXChainBridge (SoeRequired) diff --git a/include/xrpl/rdb/RelationalDatabase.h b/include/xrpl/rdb/RelationalDatabase.h index 91c282ff16..c77c8a7ed3 100644 --- a/include/xrpl/rdb/RelationalDatabase.h +++ b/include/xrpl/rdb/RelationalDatabase.h @@ -65,8 +65,10 @@ public: struct AccountTxOptions { AccountID const& account; - /// Ledger sequence range to search. A value of 0 for min or max - /// means unbounded in that direction (no constraint applied). + /** + * Ledger sequence range to search. A value of 0 for min or max + * means unbounded in that direction (no constraint applied). + */ LedgerRange ledgerRange{}; std::uint32_t offset = 0; std::uint32_t limit = 0; diff --git a/include/xrpl/rdb/SociDB.h b/include/xrpl/rdb/SociDB.h index 0f427bd18b..61edcf2263 100644 --- a/include/xrpl/rdb/SociDB.h +++ b/include/xrpl/rdb/SociDB.h @@ -1,12 +1,13 @@ #pragma once -/** An embedded database wrapper with an intuitive, type-safe interface. - - This collection of classes let's you access embedded SQLite databases - using C++ syntax that is very similar to regular SQL. - - This module requires the @ref beast_sqlite external module. -*/ +/** + * An embedded database wrapper with an intuitive, type-safe interface. + * + * This collection of classes let's you access embedded SQLite databases + * using C++ syntax that is very similar to regular SQL. + * + * This module requires the @ref beast_sqlite external module. + */ #include #include @@ -35,9 +36,9 @@ namespace xrpl { class BasicConfig; /** - DBConfig is used when a client wants to delay opening a soci::session after - parsing the config parameters. If a client want to open a session - immediately, use the free function "open" below. + * DBConfig is used when a client wants to delay opening a soci::session after + * parsing the config parameters. If a client want to open a session + * immediately, use the free function "open" below. */ class DBConfig { @@ -53,27 +54,27 @@ public: }; /** - Open a soci session. - - @param s Session to open. - - @param config Parameters to pick the soci backend and how to connect to that - backend. - - @param dbName Name of the database. This has different meaning for different - backends. Sometimes it is part of a filename (sqlite3), - other times it is a database name (postgresql). -*/ + * Open a soci session. + * + * @param s Session to open. + * + * @param config Parameters to pick the soci backend and how to connect to that + * backend. + * + * @param dbName Name of the database. This has different meaning for different + * backends. Sometimes it is part of a filename (sqlite3), + * other times it is a database name (postgresql). + */ void open(soci::session& s, BasicConfig const& config, std::string const& dbName); /** - * Open a soci session. + * Open a soci session. * - * @param s Session to open. - * @param beName Backend name. - * @param connectionString Connection string to forward to soci::open. - * see the soci::open documentation for how to use this. + * @param s Session to open. + * @param beName Backend name. + * @param connectionString Connection string to forward to soci::open. + * see the soci::open documentation for how to use this. * */ void @@ -107,11 +108,12 @@ public: checkpoint() = 0; }; -/** Returns a new checkpointer which makes checkpoints of a - soci database every checkpointPageCount pages, using a job on the job queue. - - The checkpointer contains references to the session and job queue - and so must outlive them both. +/** + * Returns a new checkpointer which makes checkpoints of a + * soci database every checkpointPageCount pages, using a job on the job queue. + * + * The checkpointer contains references to the session and job queue + * and so must outlive them both. */ std::shared_ptr makeCheckpointer(std::uintptr_t id, std::weak_ptr, JobQueue&, ServiceRegistry&); diff --git a/include/xrpl/resource/Charge.h b/include/xrpl/resource/Charge.h index 394d641e6c..12ea548fd2 100644 --- a/include/xrpl/resource/Charge.h +++ b/include/xrpl/resource/Charge.h @@ -6,28 +6,40 @@ namespace xrpl::Resource { -/** A consumption charge. */ +/** + * A consumption charge. + */ class Charge { public: - /** The type used to hold a consumption charge. */ + /** + * The type used to hold a consumption charge. + */ using value_type = int; // A default constructed Charge has no way to get a label. Delete Charge() = delete; - /** Create a charge with the specified cost and name. */ + /** + * Create a charge with the specified cost and name. + */ Charge(value_type cost, std::string label = std::string()); - /** Return the human readable label associated with the charge. */ + /** + * Return the human readable label associated with the charge. + */ [[nodiscard]] std::string const& label() const; - /** Return the cost of the charge in Resource::Manager units. */ + /** + * Return the cost of the charge in Resource::Manager units. + */ [[nodiscard]] value_type cost() const; - /** Converts this charge into a human readable string. */ + /** + * Converts this charge into a human readable string. + */ [[nodiscard]] std::string toString() const; diff --git a/include/xrpl/resource/Consumer.h b/include/xrpl/resource/Consumer.h index 18207832ac..9abcbffc82 100644 --- a/include/xrpl/resource/Consumer.h +++ b/include/xrpl/resource/Consumer.h @@ -13,7 +13,9 @@ namespace xrpl::Resource { struct Entry; class Logic; -/** An endpoint that consumes resources. */ +/** + * An endpoint that consumes resources. + */ class Consumer { private: @@ -27,42 +29,55 @@ public: Consumer& operator=(Consumer const& other); - /** Return a human readable string uniquely identifying this consumer. */ + /** + * Return a human readable string uniquely identifying this consumer. + */ [[nodiscard]] std::string toString() const; - /** Returns `true` if this is a privileged endpoint. */ + /** + * Returns `true` if this is a privileged endpoint. + */ [[nodiscard]] bool isUnlimited() const; - /** Raise the Consumer's privilege level to a Named endpoint. - The reference to the original endpoint descriptor is released. - */ + /** + * Raise the Consumer's privilege level to a Named endpoint. + * The reference to the original endpoint descriptor is released. + */ void elevate(std::string const& name); - /** Returns the current disposition of this consumer. - This should be checked upon creation to determine if the consumer - should be disconnected immediately. - */ + /** + * Returns the current disposition of this consumer. + * This should be checked upon creation to determine if the consumer + * should be disconnected immediately. + */ [[nodiscard]] Disposition disposition() const; - /** Apply a load charge to the consumer. */ + /** + * Apply a load charge to the consumer. + */ Disposition charge(Charge const& fee, std::string const& context = {}); - /** Returns `true` if the consumer should be warned. - This consumes the warning. - */ + /** + * Returns `true` if the consumer should be warned. + * This consumes the warning. + */ bool warn(); - /** Returns `true` if the consumer should be disconnected. */ + /** + * Returns `true` if the consumer should be disconnected. + */ bool disconnect(beast::Journal const& j); - /** Returns the credit balance representing consumption. */ + /** + * Returns the credit balance representing consumption. + */ int balance(); diff --git a/include/xrpl/resource/Disposition.h b/include/xrpl/resource/Disposition.h index 6dad8db19a..cd5bceafa5 100644 --- a/include/xrpl/resource/Disposition.h +++ b/include/xrpl/resource/Disposition.h @@ -2,16 +2,24 @@ namespace xrpl::Resource { -/** The disposition of a consumer after applying a load charge. */ +/** + * The disposition of a consumer after applying a load charge. + */ enum class Disposition { - /** No action required. */ + /** + * No action required. + */ Ok - /** Consumer should be warned that consumption is high. */ + /** + * Consumer should be warned that consumption is high. + */ , Warn - /** Consumer should be disconnected for excess consumption. */ + /** + * Consumer should be disconnected for excess consumption. + */ , Drop }; diff --git a/include/xrpl/resource/Fees.h b/include/xrpl/resource/Fees.h index 55d539ac6a..5001b504d6 100644 --- a/include/xrpl/resource/Fees.h +++ b/include/xrpl/resource/Fees.h @@ -4,7 +4,9 @@ namespace xrpl::Resource { -/** Schedule of fees charged for imposing load on the server. */ +/** + * Schedule of fees charged for imposing load on the server. + */ /** @{ */ extern Charge const kFeeMalformedRequest; // A request that we can immediately tell is invalid. extern Charge const kFeeRequestNoReply; // A request that we cannot satisfy. diff --git a/include/xrpl/resource/Gossip.h b/include/xrpl/resource/Gossip.h index e626af37c3..4ad5852de0 100644 --- a/include/xrpl/resource/Gossip.h +++ b/include/xrpl/resource/Gossip.h @@ -6,12 +6,16 @@ namespace xrpl::Resource { -/** Data format for exchanging consumption information across peers. */ +/** + * Data format for exchanging consumption information across peers. + */ struct Gossip { explicit Gossip() = default; - /** Describes a single consumer. */ + /** + * Describes a single consumer. + */ struct Item { explicit Item() = default; diff --git a/include/xrpl/resource/ResourceManager.h b/include/xrpl/resource/ResourceManager.h index 13e0d09343..03aab60c75 100644 --- a/include/xrpl/resource/ResourceManager.h +++ b/include/xrpl/resource/ResourceManager.h @@ -16,7 +16,9 @@ namespace xrpl::Resource { -/** Tracks load and resource consumption. */ +/** + * Tracks load and resource consumption. + */ class Manager : public beast::PropertyStream::Source { protected: @@ -25,8 +27,10 @@ protected: public: ~Manager() override = 0; - /** Create a new endpoint keyed by inbound IP address or the forwarded - * IP if proxied. */ + /** + * Create a new endpoint keyed by inbound IP address or the forwarded + * IP if proxied. + */ virtual Consumer newInboundEndpoint(beast::IP::Endpoint const& address) = 0; virtual Consumer @@ -35,27 +39,36 @@ public: bool const proxy, std::string_view forwardedFor) = 0; - /** Create a new endpoint keyed by outbound IP address and port. */ + /** + * Create a new endpoint keyed by outbound IP address and port. + */ virtual Consumer newOutboundEndpoint(beast::IP::Endpoint const& address) = 0; - /** Create a new unlimited endpoint keyed by forwarded IP. */ + /** + * Create a new unlimited endpoint keyed by forwarded IP. + */ virtual Consumer newUnlimitedEndpoint(beast::IP::Endpoint const& address) = 0; - /** Extract packaged consumer information for export. */ + /** + * Extract packaged consumer information for export. + */ virtual Gossip exportConsumers() = 0; - /** Extract consumer information for reporting. */ + /** + * Extract consumer information for reporting. + */ virtual json::Value getJson() = 0; virtual json::Value getJson(int threshold) = 0; - /** Import packaged consumer information. - @param origin An identifier that unique labels the origin. - */ + /** + * Import packaged consumer information. + * @param origin An identifier that unique labels the origin. + */ virtual void importConsumers(std::string const& origin, Gossip const& gossip) = 0; }; diff --git a/include/xrpl/resource/detail/Entry.h b/include/xrpl/resource/detail/Entry.h index 6f44ac2c29..1336bda6ab 100644 --- a/include/xrpl/resource/detail/Entry.h +++ b/include/xrpl/resource/detail/Entry.h @@ -23,8 +23,8 @@ struct Entry : public beast::List::Node Entry() = delete; /** - @param now Construction time of Entry. - */ + * @param now Construction time of Entry. + */ explicit Entry(clock_type::time_point const now) : refcount(0), localBalance(now), remoteBalance(0) { diff --git a/include/xrpl/resource/detail/Import.h b/include/xrpl/resource/detail/Import.h index a3df6fd73b..b19dbc4d1a 100644 --- a/include/xrpl/resource/detail/Import.h +++ b/include/xrpl/resource/detail/Import.h @@ -7,7 +7,9 @@ namespace xrpl::Resource { -/** A set of imported consumer data from a gossip origin. */ +/** + * A set of imported consumer data from a gossip origin. + */ struct Import { struct Item diff --git a/include/xrpl/resource/detail/Logic.h b/include/xrpl/resource/detail/Logic.h index 78cd0ac36c..3f36ad84a3 100644 --- a/include/xrpl/resource/detail/Logic.h +++ b/include/xrpl/resource/detail/Logic.h @@ -192,7 +192,9 @@ public: return getJson(kWarningThreshold); } - /** Returns a json::ValueType::Object. */ + /** + * Returns a json::ValueType::Object. + */ json::Value getJson(int threshold) { diff --git a/include/xrpl/resource/detail/Tuning.h b/include/xrpl/resource/detail/Tuning.h index 7b2046f45c..62f7fa3f9d 100644 --- a/include/xrpl/resource/detail/Tuning.h +++ b/include/xrpl/resource/detail/Tuning.h @@ -4,7 +4,9 @@ namespace xrpl::Resource { -/** Tunable constants. */ +/** + * Tunable constants. + */ // balance at which a warning is issued static constexpr auto kWarningThreshold = 5000; diff --git a/include/xrpl/server/Handoff.h b/include/xrpl/server/Handoff.h index b80a9c5745..6dc547467b 100644 --- a/include/xrpl/server/Handoff.h +++ b/include/xrpl/server/Handoff.h @@ -13,7 +13,9 @@ using http_request_type = boost::beast::http::request; -/** Used to indicate the result of a server connection handoff. */ +/** + * Used to indicate the result of a server connection handoff. + */ struct Handoff { // When `true`, the Session will close the socket. The diff --git a/include/xrpl/server/InfoSub.h b/include/xrpl/server/InfoSub.h index d708a456c4..2e9bd857c7 100644 --- a/include/xrpl/server/InfoSub.h +++ b/include/xrpl/server/InfoSub.h @@ -35,20 +35,21 @@ public: doStatus(json::Value const&) = 0; }; -/** Manages a client's subscription to data feeds. +/** + * Manages a client's subscription to data feeds. * - * An InfoSub holds a non-owning reference to its `Source` (typically the - * process-wide `NetworkOPsImp`). The destructor reaches back into the - * `Source` to remove this subscriber from every server-side subscription - * map. + * An InfoSub holds a non-owning reference to its `Source` (typically the + * process-wide `NetworkOPsImp`). The destructor reaches back into the + * `Source` to remove this subscriber from every server-side subscription + * map. * - * @note Lifetime contract: every `InfoSub` instance MUST be destroyed - * before the backing `Source`. NetworkOPsImp shutdown drops all - * subscriber strong refs before its own teardown to satisfy this. - * @note Thread-safety: per-instance state is guarded by `lock_`. The - * destructor reads tracking sets without taking `lock_` because - * the strong-pointer ref-count is zero at destruction time, so - * no other thread can be calling the public mutators. + * @note Lifetime contract: every `InfoSub` instance MUST be destroyed + * before the backing `Source`. NetworkOPsImp shutdown drops all + * subscriber strong refs before its own teardown to satisfy this. + * @note Thread-safety: per-instance state is guarded by `lock_`. The + * destructor reads tracking sets without taking `lock_` because + * the strong-pointer ref-count is zero at destruction time, so + * no other thread can be calling the public mutators. */ class InfoSub : public CountedObject { @@ -64,7 +65,8 @@ public: using Consumer = Resource::Consumer; public: - /** Abstracts the source of subscription data. + /** + * Abstracts the source of subscription data. */ class Source { @@ -217,7 +219,8 @@ public: virtual bool tryRemoveRpcSub(std::string const& strUrl) = 0; - /** Journal used by InfoSub for diagnostics that occur after the + /** + * Journal used by InfoSub for diagnostics that occur after the * owning subsystem (e.g. application-level Logs) is the only * surviving sink — primarily destructor-time cleanup failures. */ @@ -249,27 +252,29 @@ public: void deleteSubAccountInfo(AccountID const& account, bool rt); - /** Record that this subscriber is following @p book. + /** + * Record that this subscriber is following @p book. * - * Called by NetworkOPsImp::subBook so that ~InfoSub() can issue a - * matching unsubBook for every book this subscriber is tracking, - * keeping per-subscriber state symmetric with the server-side map. + * Called by NetworkOPsImp::subBook so that ~InfoSub() can issue a + * matching unsubBook for every book this subscriber is tracking, + * keeping per-subscriber state symmetric with the server-side map. * - * @param book The order book this subscriber has just subscribed to. - * @note Idempotent: re-inserting an already-tracked book is a no-op. - * @note Thread-safe: takes InfoSub::lock_. + * @param book The order book this subscriber has just subscribed to. + * @note Idempotent: re-inserting an already-tracked book is a no-op. + * @note Thread-safe: takes InfoSub::lock_. */ void insertBookSubscription(Book const& book); - /** Stop tracking @p book for this subscriber. + /** + * Stop tracking @p book for this subscriber. * - * Called by the unsubscribe RPC handler so that the book is not - * re-unsubscribed by ~InfoSub(). Pairs with insertBookSubscription. + * Called by the unsubscribe RPC handler so that the book is not + * re-unsubscribed by ~InfoSub(). Pairs with insertBookSubscription. * - * @param book The order book to forget. - * @note No-op if @p book was not previously inserted. - * @note Thread-safe: takes InfoSub::lock_. + * @param book The order book to forget. + * @note No-op if @p book was not previously inserted. + * @note Thread-safe: takes InfoSub::lock_. */ void deleteBookSubscription(Book const& book); diff --git a/include/xrpl/server/LoadFeeTrack.h b/include/xrpl/server/LoadFeeTrack.h index a19ca063d5..3afd602241 100644 --- a/include/xrpl/server/LoadFeeTrack.h +++ b/include/xrpl/server/LoadFeeTrack.h @@ -13,15 +13,16 @@ namespace xrpl { struct Fees; -/** Manages the current fee schedule. - - The "base" fee is the cost to send a reference transaction under no load, - expressed in millionths of one XRP. - - The "load" fee is how much the local server currently charges to send a - reference transaction. This fee fluctuates based on the load of the - server. -*/ +/** + * Manages the current fee schedule. + * + * The "base" fee is the cost to send a reference transaction under no load, + * expressed in millionths of one XRP. + * + * The "load" fee is how much the local server currently charges to send a + * reference transaction. This fee fluctuates based on the load of the + * server. + */ class LoadFeeTrack final { public: diff --git a/include/xrpl/server/Manifest.h b/include/xrpl/server/Manifest.h index c58c784de3..710545271a 100644 --- a/include/xrpl/server/Manifest.h +++ b/include/xrpl/server/Manifest.h @@ -68,22 +68,32 @@ namespace xrpl { struct Manifest { - /// The manifest in serialized form. + /** + * The manifest in serialized form. + */ std::string serialized; - /// The master key associated with this manifest. + /** + * The master key associated with this manifest. + */ PublicKey masterKey; - /// The ephemeral key associated with this manifest. + /** + * The ephemeral key associated with this manifest. + */ // A revoked manifest does not have a signingKey // This field is specified as "optional" in manifestFormat's // SOTemplate std::optional signingKey; - /// The sequence number of this manifest. + /** + * The sequence number of this manifest. + */ std::uint32_t sequence = 0; - /// The domain, if one was specified in the manifest; empty otherwise. + /** + * The domain, if one was specified in the manifest; empty otherwise. + */ std::string domain; Manifest() = delete; @@ -109,46 +119,61 @@ struct Manifest Manifest& operator=(Manifest&& other) = default; - /// Returns `true` if manifest signature is valid + /** + * Returns `true` if manifest signature is valid + */ [[nodiscard]] bool verify() const; - /// Returns hash of serialized manifest data + /** + * Returns hash of serialized manifest data + */ [[nodiscard]] uint256 hash() const; - /// Returns `true` if manifest revokes master key + /** + * Returns `true` if manifest revokes master key + */ // The maximum possible sequence number means that the master key has // been revoked static bool revoked(std::uint32_t sequence); - /// Returns `true` if manifest revokes master key + /** + * Returns `true` if manifest revokes master key + */ [[nodiscard]] bool revoked() const; - /// Returns manifest signature + /** + * Returns manifest signature + */ [[nodiscard]] std::optional getSignature() const; - /// Returns manifest master key signature + /** + * Returns manifest master key signature + */ [[nodiscard]] Blob getMasterSignature() const; }; -/** Format the specified manifest to a string for debugging purposes. */ +/** + * Format the specified manifest to a string for debugging purposes. + */ std::string to_string(Manifest const& m); -/** Constructs Manifest from serialized string - - @param s Serialized manifest string - - @return `std::nullopt` if string is invalid - - @note This does not verify manifest signatures. - `Manifest::verify` should be called after constructing manifest. -*/ +/** + * Constructs Manifest from serialized string + * + * @param s Serialized manifest string + * + * @return `std::nullopt` if string is invalid + * + * @note This does not verify manifest signatures. + * `Manifest::verify` should be called after constructing manifest. + */ /** @{ */ std::optional deserializeManifest(Slice s, beast::Journal journal); @@ -200,19 +225,29 @@ loadValidatorToken( beast::Journal journal = beast::Journal(beast::Journal::getNullSink())); enum class ManifestDisposition { - /// Manifest is valid + /** + * Manifest is valid + */ Accepted = 0, - /// Sequence is too old + /** + * Sequence is too old + */ Stale, - /// The master key is not acceptable to us + /** + * The master key is not acceptable to us + */ BadMasterKey, - /// The ephemeral key is not acceptable to us + /** + * The ephemeral key is not acceptable to us + */ BadEphemeralKey, - /// Timely, but invalid signature + /** + * Timely, but invalid signature + */ Invalid }; @@ -238,17 +273,23 @@ to_string(ManifestDisposition m) class DatabaseCon; -/** Remembers manifests with the highest sequence number. */ +/** + * Remembers manifests with the highest sequence number. + */ class ManifestCache { private: beast::Journal j_; std::shared_mutex mutable mutex_; - /** Active manifests stored by master public key. */ + /** + * Active manifests stored by master public key. + */ hash_map map_; - /** Master public keys stored by current ephemeral public key. */ + /** + * Master public keys stored by current ephemeral public key. + */ hash_map signingToMasterKeys_; std::atomic seq_{0}; @@ -258,104 +299,114 @@ public: { } - /** A monotonically increasing number used to detect new manifests. */ + /** + * A monotonically increasing number used to detect new manifests. + */ std::uint32_t sequence() const { return seq_.load(); } - /** Returns master key's current signing key. - - @param pk Master public key - - @return pk if no known signing key from a manifest - - @par Thread Safety - - May be called concurrently - */ + /** + * Returns master key's current signing key. + * + * @param pk Master public key + * + * @return pk if no known signing key from a manifest + * + * @par Thread Safety + * + * May be called concurrently + */ std::optional getSigningKey(PublicKey const& pk) const; - /** Returns ephemeral signing key's master public key. - - @param pk Ephemeral signing public key - - @return pk if signing key is not in a valid manifest - - @par Thread Safety - - May be called concurrently - */ + /** + * Returns ephemeral signing key's master public key. + * + * @param pk Ephemeral signing public key + * + * @return pk if signing key is not in a valid manifest + * + * @par Thread Safety + * + * May be called concurrently + */ PublicKey getMasterKey(PublicKey const& pk) const; - /** Returns master key's current manifest sequence. - - @return sequence corresponding to Master public key - if configured or std::nullopt otherwise - */ + /** + * Returns master key's current manifest sequence. + * + * @return sequence corresponding to Master public key + * if configured or std::nullopt otherwise + */ std::optional getSequence(PublicKey const& pk) const; - /** Returns domain claimed by a given public key - - @return domain corresponding to Master public key - if present, otherwise std::nullopt - */ + /** + * Returns domain claimed by a given public key + * + * @return domain corresponding to Master public key + * if present, otherwise std::nullopt + */ std::optional getDomain(PublicKey const& pk) const; - /** Returns manifest corresponding to a given public key - - @return manifest corresponding to Master public key - if present, otherwise std::nullopt - */ + /** + * Returns manifest corresponding to a given public key + * + * @return manifest corresponding to Master public key + * if present, otherwise std::nullopt + */ std::optional getManifest(PublicKey const& pk) const; - /** Returns `true` if master key has been revoked in a manifest. - - @param pk Master public key - - @par Thread Safety - - May be called concurrently - */ + /** + * Returns `true` if master key has been revoked in a manifest. + * + * @param pk Master public key + * + * @par Thread Safety + * + * May be called concurrently + */ bool revoked(PublicKey const& pk) const; - /** Add manifest to cache. - - @param m Manifest to add - - @return `ManifestDisposition::accepted` if successful, or - `stale` or `invalid` otherwise - - @par Thread Safety - - May be called concurrently - */ + /** + * Add manifest to cache. + * + * @param m Manifest to add + * + * @return `ManifestDisposition::accepted` if successful, or + * `stale` or `invalid` otherwise + * + * @par Thread Safety + * + * May be called concurrently + */ ManifestDisposition applyManifest(Manifest m); - /** Populate manifest cache with manifests in database and config. - - @param dbCon Database connection with dbTable - - @param dbTable Database table - - @param configManifest Base64 encoded manifest for local node's - validator keys - - @param configRevocation Base64 encoded validator key revocation - from the config - - @par Thread Safety - - May be called concurrently - */ + /** + * Populate manifest cache with manifests in database and config. + * + * @param dbCon Database connection with dbTable + * + * @param dbTable Database table + * + * @param configManifest Base64 encoded manifest for local node's + * validator keys + * + * @param configRevocation Base64 encoded validator key revocation + * from the config + * + * @par Thread Safety + * + * May be called concurrently + */ bool load( DatabaseCon& dbCon, @@ -363,48 +414,51 @@ public: std::string const& configManifest, std::vector const& configRevocation); - /** Populate manifest cache with manifests in database. - - @param dbCon Database connection with dbTable - - @param dbTable Database table - - @par Thread Safety - - May be called concurrently - */ + /** + * Populate manifest cache with manifests in database. + * + * @param dbCon Database connection with dbTable + * + * @param dbTable Database table + * + * @par Thread Safety + * + * May be called concurrently + */ void load(DatabaseCon& dbCon, std::string const& dbTable); - /** Save cached manifests to database. - - @param dbCon Database connection with `ValidatorManifests` table - - @param isTrusted Function that returns true if manifest is trusted - - @par Thread Safety - - May be called concurrently - */ + /** + * Save cached manifests to database. + * + * @param dbCon Database connection with `ValidatorManifests` table + * + * @param isTrusted Function that returns true if manifest is trusted + * + * @par Thread Safety + * + * May be called concurrently + */ void save( DatabaseCon& dbCon, std::string const& dbTable, std::function const& isTrusted); - /** Invokes the callback once for every populated manifest. - - @note Do not call ManifestCache member functions from within the - callback. This can re-lock the mutex from the same thread, which is UB. - @note Do not write ManifestCache member variables from within the - callback. This can lead to data races. - - @param f Function called for each manifest - - @par Thread Safety - - May be called concurrently - */ + /** + * Invokes the callback once for every populated manifest. + * + * @note Do not call ManifestCache member functions from within the + * callback. This can re-lock the mutex from the same thread, which is UB. + * @note Do not write ManifestCache member variables from within the + * callback. This can lead to data races. + * + * @param f Function called for each manifest + * + * @par Thread Safety + * + * May be called concurrently + */ template void forEachManifest(Function&& f) const @@ -417,22 +471,23 @@ public: } } - /** Invokes the callback once for every populated manifest. - - @note Do not call ManifestCache member functions from within the - callback. This can re-lock the mutex from the same thread, which is UB. - @note Do not write ManifestCache member variables from - within the callback. This can lead to data races. - - @param pf Pre-function called with the maximum number of times f will be - called (useful for memory allocations) - - @param f Function called for each manifest - - @par Thread Safety - - May be called concurrently - */ + /** + * Invokes the callback once for every populated manifest. + * + * @note Do not call ManifestCache member functions from within the + * callback. This can re-lock the mutex from the same thread, which is UB. + * @note Do not write ManifestCache member variables from + * within the callback. This can lead to data races. + * + * @param pf Pre-function called with the maximum number of times f will be + * called (useful for memory allocations) + * + * @param f Function called for each manifest + * + * @par Thread Safety + * + * May be called concurrently + */ template void forEachManifest(PreFun&& pf, EachFun&& f) const diff --git a/include/xrpl/server/NetworkOPs.h b/include/xrpl/server/NetworkOPs.h index ed2dbbb220..d3d3bb9f70 100644 --- a/include/xrpl/server/NetworkOPs.h +++ b/include/xrpl/server/NetworkOPs.h @@ -47,35 +47,37 @@ class SHAMap; // there's a functional network. // -/** Specifies the mode under which the server believes it's operating. - - This has implications about how the server processes transactions and - how it responds to requests (e.g. account balance request). - - @note Other code relies on the numerical values of these constants; do - not change them without verifying each use and ensuring that it is - not a breaking change. -*/ +/** + * Specifies the mode under which the server believes it's operating. + * + * This has implications about how the server processes transactions and + * how it responds to requests (e.g. account balance request). + * + * @note Other code relies on the numerical values of these constants; do + * not change them without verifying each use and ensuring that it is + * not a breaking change. + */ enum class OperatingMode { - DISCONNECTED = 0, //!< not ready to process requests - CONNECTED = 1, //!< convinced we are talking to the network - SYNCING = 2, //!< fallen slightly behind - TRACKING = 3, //!< convinced we agree with the network - FULL = 4 //!< we have the ledger and can even validate + DISCONNECTED = 0, ///< not ready to process requests + CONNECTED = 1, ///< convinced we are talking to the network + SYNCING = 2, ///< fallen slightly behind + TRACKING = 3, ///< convinced we agree with the network + FULL = 4 ///< we have the ledger and can even validate }; -/** Provides server functionality for clients. - - Clients include backend applications, local commands, and connected - clients. This class acts as a proxy, fulfilling the command with local - data if possible, or asking the network and returning the results if - needed. - - A backend application or local client can trust a local instance of - xrpld / NetworkOPs. However, client software connecting to non-local - instances of xrpld will need to be hardened to protect against hostile - or unreliable servers. -*/ +/** + * Provides server functionality for clients. + * + * Clients include backend applications, local commands, and connected + * clients. This class acts as a proxy, fulfilling the command with local + * data if possible, or asking the network and returning the results if + * needed. + * + * A backend application or local client can trust a local instance of + * xrpld / NetworkOPs. However, client software connecting to non-local + * instances of xrpld will need to be hardened to protect against hostile + * or unreliable servers. + */ class NetworkOPs : public InfoSub::Source { public: @@ -225,12 +227,13 @@ public: virtual json::Value getLedgerFetchInfo() = 0; - /** Accepts the current transaction tree, return the new ledger's sequence - - This API is only used via RPC with the server in STANDALONE mode and - performs a virtual consensus round, with all the transactions we are - proposing being accepted. - */ + /** + * Accepts the current transaction tree, return the new ledger's sequence + * + * This API is only used via RPC with the server in STANDALONE mode and + * performs a virtual consensus round, with all the transactions we are + * proposing being accepted. + */ virtual std::uint32_t acceptLedger(std::optional consensusDelay = std::nullopt) = 0; @@ -259,15 +262,16 @@ public: virtual void stateAccounting(json::Value& obj) = 0; - /** Total number of (book, subscriber) entries currently tracked. + /** + * Total number of (book, subscriber) entries currently tracked. * - * Counts every weak_ptr stored across every book in subBook_, NOT the - * number of distinct subscribers and NOT the number of distinct - * books: a single subscriber following N books contributes N entries. + * Counts every weak_ptr stored across every book in subBook_, NOT the + * number of distinct subscribers and NOT the number of distinct + * books: a single subscriber following N books contributes N entries. * - * @note Diagnostic accessor; intended for tests and operator visibility - * into per-book subscription state. The returned value is a - * snapshot under the subscription lock. + * @note Diagnostic accessor; intended for tests and operator visibility + * into per-book subscription state. The returned value is a + * snapshot under the subscription lock. */ virtual std::size_t getBookSubscribersCount() = 0; diff --git a/include/xrpl/server/Port.h b/include/xrpl/server/Port.h index c48a2546c1..b8bca6f95f 100644 --- a/include/xrpl/server/Port.h +++ b/include/xrpl/server/Port.h @@ -22,7 +22,9 @@ namespace xrpl { class Section; -/** Configuration information for a Server listening port. */ +/** + * Configuration information for a Server listening port. + */ struct Port { explicit Port() = default; diff --git a/include/xrpl/server/Server.h b/include/xrpl/server/Server.h index 956a414be8..f8d6005d0c 100644 --- a/include/xrpl/server/Server.h +++ b/include/xrpl/server/Server.h @@ -9,7 +9,9 @@ namespace xrpl { -/** Create the HTTP server using the specified handler. */ +/** + * Create the HTTP server using the specified handler. + */ template std::unique_ptr makeServer(Handler& handler, boost::asio::io_context& ioContext, beast::Journal journal) diff --git a/include/xrpl/server/Session.h b/include/xrpl/server/Session.h index 266570862a..be8d9a497c 100644 --- a/include/xrpl/server/Session.h +++ b/include/xrpl/server/Session.h @@ -15,11 +15,12 @@ namespace xrpl { -/** Persistent state information for a connection session. - These values are preserved between calls for efficiency. - Some fields are input parameters, some are output parameters, - and all only become defined during specific callbacks. -*/ +/** + * Persistent state information for a connection session. + * These values are preserved between calls for efficiency. + * Some fields are input parameters, some are output parameters, + * and all only become defined during specific callbacks. + */ class Session { public: @@ -29,29 +30,40 @@ public: operator=(Session const&) = delete; virtual ~Session() = default; - /** A user-definable pointer. - The initial value is always zero. - Changes to the value are persisted between calls. - */ + /** + * A user-definable pointer. + * The initial value is always zero. + * Changes to the value are persisted between calls. + */ void* tag = nullptr; - /** Returns the Journal to use for logging. */ + /** + * Returns the Journal to use for logging. + */ virtual beast::Journal journal() = 0; - /** Returns the Port settings for this connection. */ + /** + * Returns the Port settings for this connection. + */ virtual Port const& port() = 0; - /** Returns the remote address of the connection. */ + /** + * Returns the remote address of the connection. + */ virtual beast::IP::Endpoint remoteAddress() = 0; - /** Returns the current HTTP request. */ + /** + * Returns the current HTTP request. + */ virtual http_request_type& request() = 0; - /** Send a copy of data asynchronously. */ + /** + * Send a copy of data asynchronously. + */ /** @{ */ void write(std::string_view s) @@ -80,32 +92,37 @@ public: /** @} */ - /** Detach the session. - This holds the session open so that the response can be sent - asynchronously. Calls to io_context::run made by the server - will not return until all detached sessions are closed. - */ + /** + * Detach the session. + * This holds the session open so that the response can be sent + * asynchronously. Calls to io_context::run made by the server + * will not return until all detached sessions are closed. + */ virtual std::shared_ptr detach() = 0; - /** Indicate that the response is complete. - The handler should call this when it has completed writing - the response. If Keep-Alive is indicated on the connection, - this will trigger a read for the next request; else, the - connection will be closed when all remaining data has been sent. - */ + /** + * Indicate that the response is complete. + * The handler should call this when it has completed writing + * the response. If Keep-Alive is indicated on the connection, + * this will trigger a read for the next request; else, the + * connection will be closed when all remaining data has been sent. + */ virtual void complete() = 0; - /** Close the session. - This will be performed asynchronously. The session will be - closed gracefully after all pending writes have completed. - @param graceful `true` to wait until all data has finished sending. - */ + /** + * Close the session. + * This will be performed asynchronously. The session will be + * closed gracefully after all pending writes have completed. + * @param graceful `true` to wait until all data has finished sending. + */ virtual void close(bool graceful) = 0; - /** Convert the connection to WebSocket. */ + /** + * Convert the connection to WebSocket. + */ virtual std::shared_ptr websocketUpgrade() = 0; }; diff --git a/include/xrpl/server/SimpleWriter.h b/include/xrpl/server/SimpleWriter.h index 996403eafb..597e6e6d8f 100644 --- a/include/xrpl/server/SimpleWriter.h +++ b/include/xrpl/server/SimpleWriter.h @@ -14,7 +14,9 @@ namespace xrpl { -/// Deprecated: Writer that serializes a HTTP/1 message +/** + * Deprecated: Writer that serializes a HTTP/1 message + */ class SimpleWriter : public Writer { boost::beast::multi_buffer sb_; diff --git a/include/xrpl/server/WSSession.h b/include/xrpl/server/WSSession.h index 0087f9f50f..69f7decdb3 100644 --- a/include/xrpl/server/WSSession.h +++ b/include/xrpl/server/WSSession.h @@ -28,23 +28,24 @@ public: operator=(WSMsg const&) = delete; virtual ~WSMsg() = default; - /** Retrieve message data. - - Returns a tribool indicating whether or not - data is available, and a ConstBufferSequence - representing the data. - - tribool values: - maybe: Data is not ready yet - false: Data is available - true: Data is available, and - it is the last chunk of bytes. - - Derived classes that do not know when the data - ends (for example, when returning the output of a - paged database query) may return `true` and an - empty vector. - */ + /** + * Retrieve message data. + * + * Returns a tribool indicating whether or not + * data is available, and a ConstBufferSequence + * representing the data. + * + * tribool values: + * maybe: Data is not ready yet + * false: Data is available + * true: Data is available, and + * it is the last chunk of bytes. + * + * Derived classes that do not know when the data + * ends (for example, when returning the output of a + * paged database query) may return `true` and an + * empty vector. + */ virtual std::pair> prepare(std::size_t bytes, std::function resume) = 0; }; @@ -106,7 +107,9 @@ struct WSSession [[nodiscard]] virtual boost::asio::ip::tcp::endpoint const& remoteEndpoint() const = 0; - /** Send a WebSockets message. */ + /** + * Send a WebSockets message. + */ virtual void send(std::shared_ptr w) = 0; @@ -116,10 +119,11 @@ struct WSSession virtual void close(boost::beast::websocket::close_reason const& reason) = 0; - /** Indicate that the response is complete. - The handler should call this when it has completed writing - the response. - */ + /** + * Indicate that the response is complete. + * The handler should call this when it has completed writing + * the response. + */ virtual void complete() = 0; }; diff --git a/include/xrpl/server/Wallet.h b/include/xrpl/server/Wallet.h index eea44db200..ed8378989f 100644 --- a/include/xrpl/server/Wallet.h +++ b/include/xrpl/server/Wallet.h @@ -78,19 +78,22 @@ saveManifests( void addValidatorManifest(soci::session& session, std::string const& serialized); -/** Delete any saved public/private key associated with this node. */ +/** + * Delete any saved public/private key associated with this node. + */ void clearNodeIdentity(soci::session& session); -/** Returns a stable public and private key for this node. - - The node's public identity is defined by a secp256k1 keypair - that is (normally) randomly generated. This function will - return such a keypair, securely generating one if needed. - - @param session Session with the database. - - @return Pair of public and private secp256k1 keys. +/** + * Returns a stable public and private key for this node. + * + * The node's public identity is defined by a secp256k1 keypair + * that is (normally) randomly generated. This function will + * return such a keypair, securely generating one if needed. + * + * @param session Session with the database. + * + * @return Pair of public and private secp256k1 keys. */ std::pair getNodeIdentity(soci::session& session); diff --git a/include/xrpl/server/Writer.h b/include/xrpl/server/Writer.h index fe9d9519e1..a80563af43 100644 --- a/include/xrpl/server/Writer.h +++ b/include/xrpl/server/Writer.h @@ -13,26 +13,32 @@ class Writer public: virtual ~Writer() = default; - /** Returns `true` if there is no more data to pull. */ + /** + * Returns `true` if there is no more data to pull. + */ virtual bool complete() = 0; - /** Removes bytes from the input sequence. - - Can be called with 0. - */ + /** + * Removes bytes from the input sequence. + * + * Can be called with 0. + */ virtual void consume(std::size_t bytes) = 0; - /** Add data to the input sequence. - @param bytes A hint to the number of bytes desired. - @param resume A functor to later resume execution. - @return `true` if the writer is ready to provide more data. - */ + /** + * Add data to the input sequence. + * @param bytes A hint to the number of bytes desired. + * @param resume A functor to later resume execution. + * @return `true` if the writer is ready to provide more data. + */ virtual bool prepare(std::size_t bytes, std::function resume) = 0; - /** Returns a ConstBufferSequence representing the input sequence. */ + /** + * Returns a ConstBufferSequence representing the input sequence. + */ virtual std::vector data() = 0; }; diff --git a/include/xrpl/server/detail/BaseHTTPPeer.h b/include/xrpl/server/detail/BaseHTTPPeer.h index 7b35dbd4be..c7553c1da3 100644 --- a/include/xrpl/server/detail/BaseHTTPPeer.h +++ b/include/xrpl/server/detail/BaseHTTPPeer.h @@ -35,7 +35,9 @@ namespace xrpl { -/** Represents an active connection. */ +/** + * Represents an active connection. + */ template class BaseHTTPPeer : public IOList::Work, public Session { diff --git a/include/xrpl/server/detail/BaseWSPeer.h b/include/xrpl/server/detail/BaseWSPeer.h index d557140bd4..59a866ab8c 100644 --- a/include/xrpl/server/detail/BaseWSPeer.h +++ b/include/xrpl/server/detail/BaseWSPeer.h @@ -30,7 +30,9 @@ namespace xrpl { -/** Represents an active WebSocket connection. */ +/** + * Represents an active WebSocket connection. + */ template class BaseWSPeer : public BasePeer, public WSSession { @@ -48,9 +50,11 @@ private: boost::beast::multi_buffer rb_; boost::beast::multi_buffer wb_; std::list> wq_; - /// The socket has been closed, or will close after the next write - /// finishes. Do not do any more writes, and don't try to close - /// again. + /** + * The socket has been closed, or will close after the next write + * finishes. Do not do any more writes, and don't try to close + * again. + */ bool doClose_ = false; boost::beast::websocket::close_reason cr_; waitable_timer timer_; diff --git a/include/xrpl/server/detail/Door.h b/include/xrpl/server/detail/Door.h index d2d7a7baf4..285bde14ac 100644 --- a/include/xrpl/server/detail/Door.h +++ b/include/xrpl/server/detail/Door.h @@ -40,7 +40,9 @@ namespace xrpl { -/** A listening socket. */ +/** + * A listening socket. + */ template class Door : public IOList::Work, public std::enable_shared_from_this> { @@ -129,12 +131,13 @@ public: void run(); - /** Close the Door listening socket and connections. - The listening socket is closed, and all open connections - belonging to the Door are closed. - Thread Safety: - May be called concurrently - */ + /** + * Close the Door listening socket and connections. + * The listening socket is closed, and all open connections + * belonging to the Door are closed. + * Thread Safety: + * May be called concurrently + */ void close() override; diff --git a/include/xrpl/server/detail/ServerImpl.h b/include/xrpl/server/detail/ServerImpl.h index df2bba0284..c2e411cf9b 100644 --- a/include/xrpl/server/detail/ServerImpl.h +++ b/include/xrpl/server/detail/ServerImpl.h @@ -26,39 +26,45 @@ namespace xrpl { using Endpoints = std::unordered_map; -/** A multi-protocol server. - - This server maintains multiple configured listening ports, - with each listening port allows for multiple protocols including - HTTP, HTTP/S, WebSocket, Secure WebSocket, and the Peer protocol. -*/ +/** + * A multi-protocol server. + * + * This server maintains multiple configured listening ports, + * with each listening port allows for multiple protocols including + * HTTP, HTTP/S, WebSocket, Secure WebSocket, and the Peer protocol. + */ class Server { public: - /** Destroy the server. - The server is closed if it is not already closed. This call - blocks until the server has stopped. - */ + /** + * Destroy the server. + * The server is closed if it is not already closed. This call + * blocks until the server has stopped. + */ virtual ~Server() = default; - /** Returns the Journal associated with the server. */ + /** + * Returns the Journal associated with the server. + */ virtual beast::Journal journal() = 0; - /** Set the listening port settings. - This may only be called once. - */ + /** + * Set the listening port settings. + * This may only be called once. + */ virtual Endpoints ports(std::vector const& v) = 0; - /** Close the server. - The close is performed asynchronously. The handler will be notified - when the server has stopped. The server is considered stopped when - there are no pending I/O completion handlers and all connections - have closed. - Thread safety: - Safe to call concurrently from any thread. - */ + /** + * Close the server. + * The close is performed asynchronously. The handler will be notified + * when the server has stopped. The server is considered stopped when + * there are no pending I/O completion handlers and all connections + * have closed. + * Thread safety: + * Safe to call concurrently from any thread. + */ virtual void close() = 0; }; diff --git a/include/xrpl/server/detail/io_list.h b/include/xrpl/server/detail/io_list.h index 0153bd3457..7ffa85b898 100644 --- a/include/xrpl/server/detail/io_list.h +++ b/include/xrpl/server/detail/io_list.h @@ -12,7 +12,9 @@ namespace xrpl { -/** Manages a set of objects performing asynchronous I/O. */ +/** + * Manages a set of objects performing asynchronous I/O. + */ class IOList final { public: @@ -31,12 +33,13 @@ public: destroy(); } - /** Return the IOList associated with the work. - - Requirements: - The call to IOList::emplace to - create the work has already returned. - */ + /** + * Return the IOList associated with the work. + * + * Requirements: + * The call to IOList::emplace to + * create the work has already returned. + */ IOList& ios() { @@ -62,71 +65,74 @@ private: public: IOList() = default; - /** Destroy the list. - - Effects: - Closes the IOList if it was not previously - closed. No finisher is invoked in this case. - - Blocks until all work is destroyed. - */ + /** + * Destroy the list. + * + * Effects: + * Closes the IOList if it was not previously + * closed. No finisher is invoked in this case. + * + * Blocks until all work is destroyed. + */ ~IOList() { destroy(); } - /** Return `true` if the list is closed. - - Thread Safety: - Undefined result if called concurrently - with close(). - */ + /** + * Return `true` if the list is closed. + * + * Thread Safety: + * Undefined result if called concurrently + * with close(). + */ [[nodiscard]] bool closed() const { return closed_; } - /** Create associated work if not closed. - - Requirements: - `std::is_base_of_v == true` - - Thread Safety: - May be called concurrently. - - Effects: - Atomically creates, inserts, and returns new - work T, or returns nullptr if the io_list is - closed, - - If the call succeeds and returns a new object, - it is guaranteed that a subsequent call to close - will invoke Work::close on the object. - - */ + /** + * Create associated work if not closed. + * + * Requirements: + * `std::is_base_of_v == true` + * + * Thread Safety: + * May be called concurrently. + * + * Effects: + * Atomically creates, inserts, and returns new + * work T, or returns nullptr if the io_list is + * closed, + * + * If the call succeeds and returns a new object, + * it is guaranteed that a subsequent call to close + * will invoke Work::close on the object. + */ template std::shared_ptr emplace(Args&&... args); - /** Cancel active I/O. - - Thread Safety: - May not be called concurrently. - - Effects: - Associated work is closed. - - Finisher if provided, will be called when - all associated work is destroyed. The finisher - may be called from a foreign thread, or within - the call to this function. - - Only the first call to close will set the - finisher. - - No effect after the first call. - */ + /** + * Cancel active I/O. + * + * Thread Safety: + * May not be called concurrently. + * + * Effects: + * Associated work is closed. + * + * Finisher if provided, will be called when + * all associated work is destroyed. The finisher + * may be called from a foreign thread, or within + * the call to this function. + * + * Only the first call to close will set the + * finisher. + * + * No effect after the first call. + */ template void close(Finisher&& f); @@ -137,20 +143,21 @@ public: close([] {}); } - /** Block until the io_list stops. - - Effects: - The caller is blocked until the io_list is - closed and all associated work is destroyed. - - Thread safety: - May be called concurrently. - - Preconditions: - No call to io_context::run on any io_context - used by work objects associated with this io_list - exists in the caller's call stack. - */ + /** + * Block until the io_list stops. + * + * Effects: + * The caller is blocked until the io_list is + * closed and all associated work is destroyed. + * + * Thread safety: + * May be called concurrently. + * + * Preconditions: + * No call to io_context::run on any io_context + * used by work objects associated with this io_list + * exists in the caller's call stack. + */ template void join(); diff --git a/include/xrpl/shamap/Family.h b/include/xrpl/shamap/Family.h index c5bf953bfd..7624b3e600 100644 --- a/include/xrpl/shamap/Family.h +++ b/include/xrpl/shamap/Family.h @@ -35,18 +35,23 @@ public: virtual beast::Journal const& journal() = 0; - /** Return a pointer to the Family Full Below Cache */ + /** + * Return a pointer to the Family Full Below Cache + */ virtual std::shared_ptr getFullBelowCache() = 0; - /** Return a pointer to the Family Tree Node Cache */ + /** + * Return a pointer to the Family Tree Node Cache + */ virtual std::shared_ptr getTreeNodeCache() = 0; virtual void sweep() = 0; - /** Acquire ledger that has a missing node by ledger sequence + /** + * Acquire ledger that has a missing node by ledger sequence * * @param refNum Sequence of ledger to acquire. * @param nodeHash Hash of missing node to report in throw. @@ -54,7 +59,8 @@ public: virtual void missingNodeAcquireBySeq(std::uint32_t refNum, uint256 const& nodeHash) = 0; - /** Acquire ledger that has a missing node by ledger hash + /** + * Acquire ledger that has a missing node by ledger hash * * @param refHash Hash of ledger to acquire. * @param refNum Ledger sequence with missing node. diff --git a/include/xrpl/shamap/FullBelowCache.h b/include/xrpl/shamap/FullBelowCache.h index b6d1142eb4..1bb67c7453 100644 --- a/include/xrpl/shamap/FullBelowCache.h +++ b/include/xrpl/shamap/FullBelowCache.h @@ -17,9 +17,10 @@ namespace xrpl { namespace detail { -/** Remembers which tree keys have all descendants resident. - This optimizes the process of acquiring a complete tree. -*/ +/** + * Remembers which tree keys have all descendants resident. + * This optimizes the process of acquiring a complete tree. + */ class BasicFullBelowCache { private: @@ -31,13 +32,14 @@ public: using key_type = uint256; using clock_type = CacheType::clock_type; - /** Construct the cache. - - @param name A label for diagnostics and stats reporting. - @param collector The collector to use for reporting stats. - @param targetSize The cache target size. - @param targetExpirationSeconds The expiration time for items. - */ + /** + * Construct the cache. + * + * @param name A label for diagnostics and stats reporting. + * @param collector The collector to use for reporting stats. + * @param targetSize The cache target size. + * @param targetExpirationSeconds The expiration time for items. + */ BasicFullBelowCache( std::string const& name, clock_type& clock, @@ -49,59 +51,67 @@ public: { } - /** Return the clock associated with the cache. */ + /** + * Return the clock associated with the cache. + */ clock_type& clock() { return cache_.clock(); } - /** Return the number of elements in the cache. - Thread safety: - Safe to call from any thread. - */ + /** + * Return the number of elements in the cache. + * Thread safety: + * Safe to call from any thread. + */ std::size_t size() const { return cache_.size(); } - /** Remove expired cache items. - Thread safety: - Safe to call from any thread. - */ + /** + * Remove expired cache items. + * Thread safety: + * Safe to call from any thread. + */ void sweep() { cache_.sweep(); } - /** Refresh the last access time of an item, if it exists. - Thread safety: - Safe to call from any thread. - @param key The key to refresh. - @return `true` If the key exists. - */ + /** + * Refresh the last access time of an item, if it exists. + * Thread safety: + * Safe to call from any thread. + * @param key The key to refresh. + * @return `true` If the key exists. + */ bool touchIfExists(key_type const& key) { return cache_.touchIfExists(key); } - /** Insert a key into the cache. - If the key already exists, the last access time will still - be refreshed. - Thread safety: - Safe to call from any thread. - @param key The key to insert. - */ + /** + * Insert a key into the cache. + * If the key already exists, the last access time will still + * be refreshed. + * Thread safety: + * Safe to call from any thread. + * @param key The key to insert. + */ void insert(key_type const& key) { cache_.insert(key); } - /** generation determines whether cached entry is valid */ + /** + * generation determines whether cached entry is valid + */ std::uint32_t getGeneration() const { diff --git a/include/xrpl/shamap/SHAMap.h b/include/xrpl/shamap/SHAMap.h index d49e323b3f..a1194ccfd3 100644 --- a/include/xrpl/shamap/SHAMap.h +++ b/include/xrpl/shamap/SHAMap.h @@ -38,55 +38,62 @@ namespace xrpl { class SHAMapNodeID; class SHAMapSyncFilter; -/** Describes the current state of a given SHAMap */ +/** + * Describes the current state of a given SHAMap + */ enum class SHAMapState { - /** The map is in flux and objects can be added and removed. - - Example: map underlying the open ledger. + /** + * The map is in flux and objects can be added and removed. + * + * Example: map underlying the open ledger. */ Modifying = 0, - /** The map is set in stone and cannot be changed. - - Example: a map underlying a given closed ledger. + /** + * The map is set in stone and cannot be changed. + * + * Example: a map underlying a given closed ledger. */ Immutable = 1, - /** The map's hash is fixed but valid nodes may be missing and can be added. - - Example: a map that's syncing a given peer's closing ledger. + /** + * The map's hash is fixed but valid nodes may be missing and can be added. + * + * Example: a map that's syncing a given peer's closing ledger. */ Synching = 2, - /** The map is known to not be valid. - - Example: usually synching a corrupt ledger. + /** + * The map is known to not be valid. + * + * Example: usually synching a corrupt ledger. */ Invalid = 3, }; -/** A SHAMap is both a radix tree with a fan-out of 16 and a Merkle tree. - - A radix tree is a tree with two properties: - - 1. The key for a node is represented by the node's position in the tree - (the "prefix property"). - 2. A node with only one child is merged with that child - (the "merge property") - - These properties result in a significantly smaller memory footprint for - a radix tree. - - A fan-out of 16 means that each node in the tree has at most 16 - children. See https://en.wikipedia.org/wiki/Radix_tree - - A Merkle tree is a tree where each non-leaf node is labelled with the hash - of the combined labels of its children nodes. - - A key property of a Merkle tree is that testing for node inclusion is - O(log(N)) where N is the number of nodes in the tree. - - See https://en.wikipedia.org/wiki/Merkle_tree +/** + * A SHAMap is both a radix tree with a fan-out of 16 and a Merkle tree. + * + * A radix tree is a tree with two properties: + * + * 1. The key for a node is represented by the node's position in the tree + * (the "prefix property"). + * 2. A node with only one child is merged with that child + * (the "merge property") + * + * These properties result in a significantly smaller memory footprint for + * a radix tree. + * + * A fan-out of 16 means that each node in the tree has at most 16 + * children. See https://en.wikipedia.org/wiki/Radix_tree + * + * A Merkle tree is a tree where each non-leaf node is labelled with the hash + * of the combined labels of its children nodes. + * + * A key property of a Merkle tree is that testing for node inclusion is + * O(log(N)) where N is the number of nodes in the tree. + * + * See https://en.wikipedia.org/wiki/Merkle_tree */ class SHAMap { @@ -94,10 +101,14 @@ private: Family& f_; beast::Journal journal_; - /** ID to distinguish this map for all others we're sharing nodes with. */ + /** + * ID to distinguish this map for all others we're sharing nodes with. + */ std::uint32_t cowid_ = 1; - /** The sequence of the ledger that this map references, if any. */ + /** + * The sequence of the ledger that this map references, if any. + */ std::uint32_t ledgerSeq_ = 0; SHAMapTreeNodePtr root_; @@ -107,11 +118,15 @@ private: mutable bool full_ = false; // Map is believed complete in database public: - /** Number of children each non-leaf node has (the 'radix tree' part of the - * map) */ + /** + * Number of children each non-leaf node has (the 'radix tree' part of the + * map) + */ static constexpr unsigned int kBranchFactor = SHAMapInnerNode::kBranchFactor; - /** The depth of the hash map: data is only present in the leaves */ + /** + * The depth of the hash map: data is only present in the leaves + */ static constexpr unsigned int kLeafDepth = 64; using DeltaItem = @@ -147,10 +162,11 @@ public: //-------------------------------------------------------------------------- - /** Iterator to a SHAMap's leaves - This is always a const iterator. - Meets the requirements of ForwardRange. - */ + /** + * Iterator to a SHAMap's leaves + * This is always a const iterator. + * Meets the requirements of ForwardRange. + */ class ConstIterator; ConstIterator @@ -180,7 +196,9 @@ public: // normal hash access functions - /** Does the tree have an item with the given ID? */ + /** + * Does the tree have an item with the given ID? + */ bool hasItem(uint256 const& id) const; @@ -208,60 +226,66 @@ public: peekItem(uint256 const& id, SHAMapHash& hash) const; // traverse functions - /** Find the first item after the given item. - - @param id the identifier of the item. - - @note The item does not need to exist. + /** + * Find the first item after the given item. + * + * @param id the identifier of the item. + * + * @note The item does not need to exist. */ ConstIterator upperBound(uint256 const& id) const; - /** Find the object with the greatest object id smaller than the input id. - - @param id the identifier of the item. - - @note The item does not need to exist. + /** + * Find the object with the greatest object id smaller than the input id. + * + * @param id the identifier of the item. + * + * @note The item does not need to exist. */ ConstIterator lowerBound(uint256 const& id) const; - /** Visit every node in this SHAMap - - @param function called with every node visited. - If function returns false, visitNodes exits. - */ + /** + * Visit every node in this SHAMap + * + * @param function called with every node visited. + * If function returns false, visitNodes exits. + */ void visitNodes(std::function const& function) const; - /** Visit every node in this SHAMap that - is not present in the specified SHAMap - - @param function called with every node visited. - If function returns false, visitDifferences exits. - */ + /** + * Visit every node in this SHAMap that + * is not present in the specified SHAMap + * + * @param function called with every node visited. + * If function returns false, visitDifferences exits. + */ void visitDifferences(SHAMap const* have, std::function const&) const; - /** Visit every leaf node in this SHAMap - - @param function called with every non inner node visited. - */ + /** + * Visit every leaf node in this SHAMap + * + * @param function called with every non inner node visited. + */ void visitLeaves(std::function const&)> const&) const; // comparison/sync functions - /** Check for nodes in the SHAMap not available - - Traverse the SHAMap efficiently, maximizing I/O - concurrency, to discover nodes referenced in the - SHAMap but not available locally. - - @param maxNodes The maximum number of found nodes to return - @param filter The filter to use when retrieving nodes - @param return The nodes known to be missing - */ + /** + * Check for nodes in the SHAMap not available + * + * Traverse the SHAMap efficiently, maximizing I/O + * concurrency, to discover nodes referenced in the + * SHAMap but not available locally. + * + * @param maxNodes The maximum number of found nodes to return + * @param filter The filter to use when retrieving nodes + * @param return The nodes known to be missing + */ std::vector> getMissingNodes(int maxNodes, SHAMapSyncFilter const* filter); @@ -291,7 +315,9 @@ public: static bool verifyProofPath(uint256 const& rootHash, uint256 const& key, std::vector const& path); - /** Serializes the root in a format appropriate for sending over the wire */ + /** + * Serializes the root in a format appropriate for sending over the wire + */ void serializeRoot(Serializer& s) const; @@ -317,11 +343,15 @@ public: bool compare(SHAMap const& otherMap, Delta& differences, int maxCount) const; - /** Convert any modified nodes to shared. */ + /** + * Convert any modified nodes to shared. + */ int unshare(); - /** Flush modified nodes to the nodestore and convert them to shared. */ + /** + * Flush modified nodes to the nodestore and convert them to shared. + */ int flushDirty(NodeObjectType t); @@ -364,30 +394,42 @@ private: SHAMapTreeNodePtr checkFilter(SHAMapHash const& hash, SHAMapSyncFilter const* filter) const; - /** Update hashes up to the root */ + /** + * Update hashes up to the root + */ void dirtyUp(SharedPtrNodeStack& stack, uint256 const& target, SHAMapTreeNodePtr terminal); - /** Walk towards the specified id, returning the node. Caller must check - if the return is nullptr, and if not, if the node->peekItem()->key() == - id */ + /** + * Walk towards the specified id, returning the node. Caller must check + * if the return is nullptr, and if not, if the node->peekItem()->key() == + * id + */ SHAMapLeafNode* walkTowardsKey(uint256 const& id, SharedPtrNodeStack* stack = nullptr) const; - /** Return nullptr if key not found */ + /** + * Return nullptr if key not found + */ SHAMapLeafNode* findKey(uint256 const& id) const; - /** Unshare the node, allowing it to be modified */ + /** + * Unshare the node, allowing it to be modified + */ template intr_ptr::SharedPtr unshareNode(intr_ptr::SharedPtr, SHAMapNodeID const& nodeID); - /** prepare a node to be modified before flushing */ + /** + * prepare a node to be modified before flushing + */ template intr_ptr::SharedPtr preFlushNode(intr_ptr::SharedPtr node) const; - /** write and canonicalize modified node */ + /** + * write and canonicalize modified node + */ SHAMapTreeNodePtr writeNode(NodeObjectType t, SHAMapTreeNodePtr node) const; @@ -442,7 +484,9 @@ private: SHAMapTreeNodePtr descendNoStore(SHAMapInnerNode&, int branch) const; - /** If there is only one leaf below this node, get its contents */ + /** + * If there is only one leaf below this node, get its contents + */ boost::intrusive_ptr const& onlyBelow(SHAMapTreeNode*) const; diff --git a/include/xrpl/shamap/SHAMapAccountStateLeafNode.h b/include/xrpl/shamap/SHAMapAccountStateLeafNode.h index ee81107f61..96c853eb28 100644 --- a/include/xrpl/shamap/SHAMapAccountStateLeafNode.h +++ b/include/xrpl/shamap/SHAMapAccountStateLeafNode.h @@ -15,7 +15,9 @@ namespace xrpl { -/** A leaf node for a state object. */ +/** + * A leaf node for a state object. + */ class SHAMapAccountStateLeafNode final : public SHAMapLeafNode, public CountedObject { diff --git a/include/xrpl/shamap/SHAMapInnerNode.h b/include/xrpl/shamap/SHAMapInnerNode.h index 0fb4e24077..44d3bd6279 100644 --- a/include/xrpl/shamap/SHAMapInnerNode.h +++ b/include/xrpl/shamap/SHAMapInnerNode.h @@ -18,63 +18,72 @@ namespace xrpl { class SHAMapInnerNode final : public SHAMapTreeNode, public CountedObject { public: - /** Each inner node has 16 children (the 'radix tree' part of the map) */ + /** + * Each inner node has 16 children (the 'radix tree' part of the map) + */ static constexpr unsigned int kBranchFactor = 16; private: - /** Opaque type that contains the `hashes` array (array of type - `SHAMapHash`) and the `children` array (array of type - `intr_ptr::SharedPtr`). + /** + * Opaque type that contains the `hashes` array (array of type + * `SHAMapHash`) and the `children` array (array of type + * `intr_ptr::SharedPtr`). */ TaggedPointer hashesAndChildren_; std::uint32_t fullBelowGen_ = 0; std::uint16_t isBranch_ = 0; - /** A bitlock for the children of this node, with one bit per child */ + /** + * A bitlock for the children of this node, with one bit per child + */ mutable std::atomic lock_ = 0; - /** Convert arrays stored in `hashesAndChildren_` so they can store the - requested number of children. - - @param toAllocate allocate space for at least this number of children - (must be <= branchFactor) - - @note the arrays may allocate more than the requested value in - `toAllocate`. This is due to the implementation of TagPointer, which - only supports allocating arrays of 4 different sizes. + /** + * Convert arrays stored in `hashesAndChildren_` so they can store the + * requested number of children. + * + * @param toAllocate allocate space for at least this number of children + * (must be <= branchFactor) + * + * @note the arrays may allocate more than the requested value in + * `toAllocate`. This is due to the implementation of TagPointer, which + * only supports allocating arrays of 4 different sizes. */ void resizeChildArrays(std::uint8_t toAllocate); - /** Get the child's index inside the `hashes` or `children` array (stored in - `hashesAndChildren_`. - - These arrays may or may not be sparse). The optional will be empty is an - empty branch is requested and the arrays are sparse. - - @param i index of the requested child + /** + * Get the child's index inside the `hashes` or `children` array (stored in + * `hashesAndChildren_`. + * + * These arrays may or may not be sparse). The optional will be empty is an + * empty branch is requested and the arrays are sparse. + * + * @param i index of the requested child */ std::optional getChildIndex(int i) const; - /** Call the `f` callback for all 16 (branchFactor) branches - even if - the branch is empty. - - @param f a one parameter callback function. The parameter is the - child's hash. - */ + /** + * Call the `f` callback for all 16 (branchFactor) branches - even if + * the branch is empty. + * + * @param f a one parameter callback function. The parameter is the + * child's hash. + */ template void iterChildren(F&& f) const; - /** Call the `f` callback for all non-empty branches. - - @param f a two parameter callback function. The first parameter is - the branch number, the second parameter is the index into the array. - For dense formats these are the same, for sparse they may be - different. - */ + /** + * Call the `f` callback for all non-empty branches. + * + * @param f a two parameter callback function. The first parameter is + * the branch number, the second parameter is the index into the array. + * For dense formats these are the same, for sparse they may be + * different. + */ template void iterNonEmptyChildIndexes(F&& f) const; @@ -149,7 +158,9 @@ public: void updateHash() override; - /** Recalculate the hash of all children and this node. */ + /** + * Recalculate the hash of all children and this node. + */ void updateHashDeep(); diff --git a/include/xrpl/shamap/SHAMapItem.h b/include/xrpl/shamap/SHAMapItem.h index 846f0bab4e..d45d75a942 100644 --- a/include/xrpl/shamap/SHAMapItem.h +++ b/include/xrpl/shamap/SHAMapItem.h @@ -138,6 +138,7 @@ intrusive_ptr_release(SHAMapItem const* x) // If the slabber doesn't claim this pointer, it was allocated // manually, so we free it manually. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) if (!detail::gSlabber.deallocate(const_cast(p))) delete[] p; } diff --git a/include/xrpl/shamap/SHAMapLeafNode.h b/include/xrpl/shamap/SHAMapLeafNode.h index 112c243131..26cfde9fe8 100644 --- a/include/xrpl/shamap/SHAMapLeafNode.h +++ b/include/xrpl/shamap/SHAMapLeafNode.h @@ -46,11 +46,12 @@ public: boost::intrusive_ptr const& peekItem() const; - /** Set the item that this node points to and update the node's hash. - - @param i the new item - @return false if the change was, effectively, a noop (that is, if the - hash was unchanged); true otherwise. + /** + * Set the item that this node points to and update the node's hash. + * + * @param i the new item + * @return false if the change was, effectively, a noop (that is, if the + * hash was unchanged); true otherwise. */ bool setItem(boost::intrusive_ptr i); diff --git a/include/xrpl/shamap/SHAMapNodeID.h b/include/xrpl/shamap/SHAMapNodeID.h index b812c10ca9..6094892091 100644 --- a/include/xrpl/shamap/SHAMapNodeID.h +++ b/include/xrpl/shamap/SHAMapNodeID.h @@ -12,7 +12,9 @@ namespace xrpl { -/** Identifies a node inside a SHAMap */ +/** + * Identifies a node inside a SHAMap + */ class SHAMapNodeID : public CountedObject { private: @@ -64,7 +66,9 @@ public: createID(int depth, uint256 const& key); // FIXME-C++20: use spaceship and operator synthesis - /** Comparison operators */ + /** + * Comparison operators + */ bool operator<(SHAMapNodeID const& n) const { @@ -117,7 +121,8 @@ operator<<(std::ostream& out, SHAMapNodeID const& node) return out << to_string(node); } -/** Return an object representing a serialized SHAMap Node ID +/** + * Return an object representing a serialized SHAMap Node ID * * @param s A string of bytes * @param data a non-null pointer to a buffer of @param size bytes. @@ -136,7 +141,9 @@ deserializeSHAMapNodeID(std::string_view s) } /** @} */ -/** Returns the branch that would contain the given hash */ +/** + * Returns the branch that would contain the given hash + */ [[nodiscard]] unsigned int selectBranch(SHAMapNodeID const& id, uint256 const& hash); diff --git a/include/xrpl/shamap/SHAMapSyncFilter.h b/include/xrpl/shamap/SHAMapSyncFilter.h index b6ce175915..60f340db3c 100644 --- a/include/xrpl/shamap/SHAMapSyncFilter.h +++ b/include/xrpl/shamap/SHAMapSyncFilter.h @@ -7,7 +7,9 @@ #include #include -/** Callback for filtering SHAMap during sync. */ +/** + * Callback for filtering SHAMap during sync. + */ namespace xrpl { class SHAMapSyncFilter diff --git a/include/xrpl/shamap/SHAMapTreeNode.h b/include/xrpl/shamap/SHAMapTreeNode.h index 1eebbaa17f..c8b242238a 100644 --- a/include/xrpl/shamap/SHAMapTreeNode.h +++ b/include/xrpl/shamap/SHAMapTreeNode.h @@ -39,18 +39,20 @@ class SHAMapTreeNode : public IntrusiveRefCounts protected: SHAMapHash hash_; - /** Determines the owning SHAMap, if any. Used for copy-on-write semantics. - - If this value is 0, the node is not dirty and does not need to be - flushed. It is eligible for sharing and may be included multiple - SHAMap instances. + /** + * Determines the owning SHAMap, if any. Used for copy-on-write semantics. + * + * If this value is 0, the node is not dirty and does not need to be + * flushed. It is eligible for sharing and may be included multiple + * SHAMap instances. */ std::uint32_t cowid_; - /** Construct a node - - @param cowid The identifier of a SHAMap. For more, see #cowid_ - @param hash The hash associated with this node, if any. + /** + * Construct a node + * + * @param cowid The identifier of a SHAMap. For more, see #cowid_ + * @param hash The hash associated with this node, if any. */ /** @{ */ explicit SHAMapTreeNode(std::uint32_t cowid) noexcept : cowid_(cowid) @@ -74,28 +76,30 @@ public: virtual void partialDestructor() {}; - /** \defgroup SHAMap Copy-on-Write Support - - By nature, a node may appear in multiple SHAMap instances. Rather - than actually duplicating these nodes, SHAMap opts to be memory - efficient and uses copy-on-write semantics for nodes. - - Only nodes that are not modified and don't need to be flushed back - can be shared. Once a node needs to be changed, it must first be - copied and the copy must marked as not shareable. - - Note that just because a node may not be *owned* by a given SHAMap - instance does not mean that the node is NOT a part of any SHAMap. It - only means that the node is not owned exclusively by any one SHAMap. - - For more on copy-on-write, check out: - https://en.wikipedia.org/wiki/Copy-on-write + /** + * @defgroup SHAMap Copy-on-Write Support + * + * By nature, a node may appear in multiple SHAMap instances. Rather + * than actually duplicating these nodes, SHAMap opts to be memory + * efficient and uses copy-on-write semantics for nodes. + * + * Only nodes that are not modified and don't need to be flushed back + * can be shared. Once a node needs to be changed, it must first be + * copied and the copy must marked as not shareable. + * + * Note that just because a node may not be *owned* by a given SHAMap + * instance does not mean that the node is NOT a part of any SHAMap. It + * only means that the node is not owned exclusively by any one SHAMap. + * + * For more on copy-on-write, check out: + * https://en.wikipedia.org/wiki/Copy-on-write */ /** @{ */ - /** Returns the SHAMap that owns this node. - - @return the ID of the SHAMap that owns this node, or 0 if the - node is not owned by any SHAMap and is a candidate for sharing. + /** + * Returns the SHAMap that owns this node. + * + * @return the ID of the SHAMap that owns this node, or 0 if the + * node is not owned by any SHAMap and is a candidate for sharing. */ std::uint32_t cowid() const @@ -103,10 +107,11 @@ public: return cowid_; } - /** If this node is shared with another map, mark it as no longer shared. - - Only nodes that are not modified and do not need to be flushed back - should be marked as unshared. + /** + * If this node is shared with another map, mark it as no longer shared. + * + * Only nodes that are not modified and do not need to be flushed back + * should be marked as unshared. */ void unshare() @@ -114,39 +119,55 @@ public: cowid_ = 0; } - /** Make a copy of this node, setting the owner. */ + /** + * Make a copy of this node, setting the owner. + */ virtual SHAMapTreeNodePtr clone(std::uint32_t cowid) const = 0; /** @} */ - /** Recalculate the hash of this node. */ + /** + * Recalculate the hash of this node. + */ virtual void updateHash() = 0; - /** Return the hash of this node. */ + /** + * Return the hash of this node. + */ SHAMapHash const& getHash() const { return hash_; } - /** Determines the type of node. */ + /** + * Determines the type of node. + */ virtual SHAMapNodeType getType() const = 0; - /** Determines if this is a leaf node. */ + /** + * Determines if this is a leaf node. + */ virtual bool isLeaf() const = 0; - /** Determines if this is an inner node. */ + /** + * Determines if this is an inner node. + */ virtual bool isInner() const = 0; - /** Serialize the node in a format appropriate for sending over the wire */ + /** + * Serialize the node in a format appropriate for sending over the wire + */ virtual void serializeForWire(Serializer&) const = 0; - /** Serialize the node in a format appropriate for hashing */ + /** + * Serialize the node in a format appropriate for hashing + */ virtual void serializeWithPrefix(Serializer&) const = 0; diff --git a/include/xrpl/shamap/SHAMapTxLeafNode.h b/include/xrpl/shamap/SHAMapTxLeafNode.h index 86186434f8..9b9ac2f996 100644 --- a/include/xrpl/shamap/SHAMapTxLeafNode.h +++ b/include/xrpl/shamap/SHAMapTxLeafNode.h @@ -15,7 +15,9 @@ namespace xrpl { -/** A leaf node for a transaction. No metadata is included. */ +/** + * A leaf node for a transaction. No metadata is included. + */ class SHAMapTxLeafNode final : public SHAMapLeafNode, public CountedObject { public: diff --git a/include/xrpl/shamap/SHAMapTxPlusMetaLeafNode.h b/include/xrpl/shamap/SHAMapTxPlusMetaLeafNode.h index 9e4573d45b..6f8a7ebfda 100644 --- a/include/xrpl/shamap/SHAMapTxPlusMetaLeafNode.h +++ b/include/xrpl/shamap/SHAMapTxPlusMetaLeafNode.h @@ -15,7 +15,9 @@ namespace xrpl { -/** A leaf node for a transaction and its associated metadata. */ +/** + * A leaf node for a transaction and its associated metadata. + */ class SHAMapTxPlusMetaLeafNode final : public SHAMapLeafNode, public CountedObject { diff --git a/include/xrpl/shamap/detail/TaggedPointer.h b/include/xrpl/shamap/detail/TaggedPointer.h index 79f3464d5b..509e6cc58d 100644 --- a/include/xrpl/shamap/detail/TaggedPointer.h +++ b/include/xrpl/shamap/detail/TaggedPointer.h @@ -12,111 +12,122 @@ namespace xrpl { -/** TaggedPointer is a combination of a pointer and a mask stored in the - lowest two bits. - - Since pointers do not have arbitrary alignment, the lowest bits in the - pointer are guaranteed to be zero. TaggedPointer stores information in these - low bits. When dereferencing the pointer, these low "tag" bits are set to - zero. When accessing the tag bits, the high "pointer" bits are set to zero. - - The "pointer" part points to the equivalent to an array of - `SHAMapHash` followed immediately by an array of - `shared_ptr`. The sizes of these arrays are - determined by the tag. The tag is an index into an array (`boundaries`, - defined in the cpp file) that specifies the size. Both arrays are the - same size. Note that the sizes may be smaller than the full 16 elements - needed to explicitly store all the children. In this case, the arrays - only store the non-empty children. The non-empty children are stored in - index order. For example, if only children `2` and `14` are non-empty, a - two-element array would store child `2` in array index 0 and child `14` - in array index 1. There are functions to convert between a child's tree - index and the child's index in a sparse array. - - The motivation for this class is saving RAM. A large percentage of inner - nodes only store a small number of children. Memory can be saved by - storing the inner node's children in sparse arrays. Measurements show - that on average a typical SHAMap's inner nodes can be stored using only - 25% of the original space. -*/ +/** + * TaggedPointer is a combination of a pointer and a mask stored in the + * lowest two bits. + * + * Since pointers do not have arbitrary alignment, the lowest bits in the + * pointer are guaranteed to be zero. TaggedPointer stores information in these + * low bits. When dereferencing the pointer, these low "tag" bits are set to + * zero. When accessing the tag bits, the high "pointer" bits are set to zero. + * + * The "pointer" part points to the equivalent to an array of + * `SHAMapHash` followed immediately by an array of + * `shared_ptr`. The sizes of these arrays are + * determined by the tag. The tag is an index into an array (`boundaries`, + * defined in the cpp file) that specifies the size. Both arrays are the + * same size. Note that the sizes may be smaller than the full 16 elements + * needed to explicitly store all the children. In this case, the arrays + * only store the non-empty children. The non-empty children are stored in + * index order. For example, if only children `2` and `14` are non-empty, a + * two-element array would store child `2` in array index 0 and child `14` + * in array index 1. There are functions to convert between a child's tree + * index and the child's index in a sparse array. + * + * The motivation for this class is saving RAM. A large percentage of inner + * nodes only store a small number of children. Memory can be saved by + * storing the inner node's children in sparse arrays. Measurements show + * that on average a typical SHAMap's inner nodes can be stored using only + * 25% of the original space. + */ class TaggedPointer { private: static_assert( alignof(SHAMapHash) >= 4, "Bad alignment: Tag pointer requires low two bits to be zero."); - /** Upper bits are the pointer, lowest two bits are the tag - A moved-from object will have a tp_ of zero. - */ + /** + * Upper bits are the pointer, lowest two bits are the tag + * A moved-from object will have a tp_ of zero. + */ std::uintptr_t tp_ = 0; - /** bit-and with this mask to get the tag bits (lowest two bits) */ + /** + * bit-and with this mask to get the tag bits (lowest two bits) + */ static constexpr std::uintptr_t kTagMask = 3; - /** bit-and with this mask to get the pointer bits (mask out the tag) */ + /** + * bit-and with this mask to get the pointer bits (mask out the tag) + */ static constexpr std::uintptr_t kPtrMask = ~kTagMask; - /** Deallocate memory and run destructors */ + /** + * Deallocate memory and run destructors + */ void destroyHashesAndChildren(); struct RawAllocateTag { }; - /** This constructor allocates space for the hashes and children, but - does not run constructors. - - @param RawAllocateTag used to select overload only - - @param numChildren allocate space for at least this number of children - (must be <= branchFactor) - - @note Since the hashes/children destructors are always run in the - TaggedPointer destructor, this means those constructors _must_ be run - after this constructor is run. This constructor is private and only used - in places where the hashes/children constructor are subsequently run. - */ + /** + * This constructor allocates space for the hashes and children, but + * does not run constructors. + * + * @param RawAllocateTag used to select overload only + * + * @param numChildren allocate space for at least this number of children + * (must be <= branchFactor) + * + * @note Since the hashes/children destructors are always run in the + * TaggedPointer destructor, this means those constructors _must_ be run + * after this constructor is run. This constructor is private and only used + * in places where the hashes/children constructor are subsequently run. + */ explicit TaggedPointer(RawAllocateTag, std::uint8_t numChildren); public: TaggedPointer() = delete; explicit TaggedPointer(std::uint8_t numChildren); - /** Constructor is used change the number of allocated children. - - Existing children from `other` are copied (toAllocate must be >= the - number of children). The motivation for making this a constructor is it - saves unneeded copying and zeroing out of hashes if this were - implemented directly in the SHAMapInnerNode class. - - @param other children and hashes are moved from this param - - @param isBranch bitset of non-empty children in `other` - - @param toAllocate allocate space for at least this number of children - (must be <= branchFactor) - */ + /** + * Constructor is used change the number of allocated children. + * + * Existing children from `other` are copied (toAllocate must be >= the + * number of children). The motivation for making this a constructor is it + * saves unneeded copying and zeroing out of hashes if this were + * implemented directly in the SHAMapInnerNode class. + * + * @param other children and hashes are moved from this param + * + * @param isBranch bitset of non-empty children in `other` + * + * @param toAllocate allocate space for at least this number of children + * (must be <= branchFactor) + */ explicit TaggedPointer(TaggedPointer&& other, std::uint16_t isBranch, std::uint8_t toAllocate); - /** Given `other` with the specified children in `srcBranches`, create a - new TaggedPointer with the allocated number of children and the - children specified in `dstBranches`. - - @param other children and hashes are moved from this param - - @param srcBranches bitset of non-empty children in `other` - - @param dstBranches bitset of children to copy from `other` (or space to - leave in a sparse array - see note below) - - @param toAllocate allocate space for at least this number of children - (must be <= branchFactor) - - @note a child may be absent in srcBranches but present in dstBranches - (if dst has a sparse representation, space for the new child will be - left in the sparse array). Typically, srcBranches and dstBranches will - differ by at most one bit. The function works correctly if they differ - by more, but there are likely more efficient algorithms to consider if - this becomes a common use-case. - */ + /** + * Given `other` with the specified children in `srcBranches`, create a + * new TaggedPointer with the allocated number of children and the + * children specified in `dstBranches`. + * + * @param other children and hashes are moved from this param + * + * @param srcBranches bitset of non-empty children in `other` + * + * @param dstBranches bitset of children to copy from `other` (or space to + * leave in a sparse array - see note below) + * + * @param toAllocate allocate space for at least this number of children + * (must be <= branchFactor) + * + * @note a child may be absent in srcBranches but present in dstBranches + * (if dst has a sparse representation, space for the new child will be + * left in the sparse array). Typically, srcBranches and dstBranches will + * differ by at most one bit. The function works correctly if they differ + * by more, but there are likely more efficient algorithms to consider if + * this becomes a common use-case. + */ explicit TaggedPointer( TaggedPointer&& other, std::uint16_t srcBranches, @@ -132,68 +143,81 @@ public: ~TaggedPointer(); - /** Decode the tagged pointer into its tag and pointer */ + /** + * Decode the tagged pointer into its tag and pointer + */ [[nodiscard]] std::pair decode() const; - /** Get the number of elements allocated for each array */ + /** + * Get the number of elements allocated for each array + */ [[nodiscard]] std::uint8_t capacity() const; - /** Check if the arrays have a dense format. - - @note The dense format is when there is an array element for all 16 - (branchFactor) possible children. - */ + /** + * Check if the arrays have a dense format. + * + * @note The dense format is when there is an array element for all 16 + * (branchFactor) possible children. + */ [[nodiscard]] bool isDense() const; - /** Get the number of elements in each array and a pointer to the start - of each array. - */ + /** + * Get the number of elements in each array and a pointer to the start + * of each array. + */ [[nodiscard]] std::tuple getHashesAndChildren() const; - /** Get the `hashes` array */ + /** + * Get the `hashes` array + */ [[nodiscard]] SHAMapHash* getHashes() const; - /** Get the `children` array */ + /** + * Get the `children` array + */ [[nodiscard]] SHAMapTreeNodePtr* getChildren() const; - /** Call the `f` callback for all 16 (branchFactor) branches - even if - the branch is empty. - - @param isBranch bitset of non-empty children - - @param f a one parameter callback function. The parameter is the - child's hash. + /** + * Call the `f` callback for all 16 (branchFactor) branches - even if + * the branch is empty. + * + * @param isBranch bitset of non-empty children + * + * @param f a one parameter callback function. The parameter is the + * child's hash. */ template void iterChildren(std::uint16_t isBranch, F&& f) const; - /** Call the `f` callback for all non-empty branches. - - @param isBranch bitset of non-empty children - - @param f a two parameter callback function. The first parameter is - the branch number, the second parameter is the index into the array. - For dense formats these are the same, for sparse they may be - different. + /** + * Call the `f` callback for all non-empty branches. + * + * @param isBranch bitset of non-empty children + * + * @param f a two parameter callback function. The first parameter is + * the branch number, the second parameter is the index into the array. + * For dense formats these are the same, for sparse they may be + * different. */ template void iterNonEmptyChildIndexes(std::uint16_t isBranch, F&& f) const; - /** Get the child's index inside the `hashes` or `children` array (which - may or may not be sparse). The optional will be empty if an empty - branch is requested and the children are sparse. - - @param isBranch bitset of non-empty children - - @param i index of the requested child + /** + * Get the child's index inside the `hashes` or `children` array (which + * may or may not be sparse). The optional will be empty if an empty + * branch is requested and the children are sparse. + * + * @param isBranch bitset of non-empty children + * + * @param i index of the requested child */ [[nodiscard]] std::optional getChildIndex(std::uint16_t isBranch, int i) const; diff --git a/include/xrpl/shamap/detail/TaggedPointer.ipp b/include/xrpl/shamap/detail/TaggedPointer.ipp index 6606c49a6b..9275f3d15a 100644 --- a/include/xrpl/shamap/detail/TaggedPointer.ipp +++ b/include/xrpl/shamap/detail/TaggedPointer.ipp @@ -507,6 +507,9 @@ TaggedPointer::operator=(TaggedPointer&& other) [[nodiscard]] inline std::pair TaggedPointer::decode() const { + // tp_ packs a raw pointer together with the tag bits; recovering the + // pointer inherently requires an integer-to-pointer cast. + // NOLINTNEXTLINE(performance-no-int-to-ptr) return {tp_ & kTagMask, reinterpret_cast(tp_ & kPtrMask)}; } @@ -535,6 +538,9 @@ TaggedPointer::getHashesAndChildren() const [[nodiscard]] inline SHAMapHash* TaggedPointer::getHashes() const { + // tp_ packs a raw pointer together with the tag bits; recovering the + // pointer inherently requires an integer-to-pointer cast. + // NOLINTNEXTLINE(performance-no-int-to-ptr) return reinterpret_cast(tp_ & kPtrMask); }; diff --git a/include/xrpl/tx/ApplyContext.h b/include/xrpl/tx/ApplyContext.h index ca778387cd..472afdf624 100644 --- a/include/xrpl/tx/ApplyContext.h +++ b/include/xrpl/tx/ApplyContext.h @@ -21,7 +21,9 @@ namespace xrpl { -/** State information when applying a tx. */ +/** + * State information when applying a tx. + */ class ApplyContext { public: @@ -83,7 +85,9 @@ public: return flags_; } - /** Sets the DeliveredAmount field in the metadata */ + /** + * Sets the DeliveredAmount field in the metadata + */ void deliver(STAmount const& amount) { @@ -91,18 +95,26 @@ public: view_->deliver(amount); } - /** Discard changes and start fresh. */ + /** + * Discard changes and start fresh. + */ void discard(); - /** Apply the transaction result to the base. */ + /** + * Apply the transaction result to the base. + */ std::optional apply(TER); - /** Get the number of unapplied changes. */ + /** + * Get the number of unapplied changes. + */ std::size_t size(); - /** Visit unapplied changes. */ + /** + * Visit unapplied changes. + */ void visit( std::functionrawDestroyXRP(fee); } - /** Applies all invariant checkers one by one. - - @param result the result generated by processing this transaction. - @param fee the fee charged for this transaction - @return the result code that should be returned for this transaction. + /** + * Applies all invariant checkers one by one. + * + * @param result the result generated by processing this transaction. + * @param fee the fee charged for this transaction + * @return the result code that should be returned for this transaction. */ TER checkInvariants(TER const result, XRPAmount const fee); + ApplyViewContext + getApplyViewContext() + { + XRPL_ASSERT( + view_.has_value(), + "xrpl::ApplyContext::getApplyViewContext : view_ emplaced in constructor"); + return {.view = *view_, .tx = tx}; + } + private: static TER failInvariantCheck(TER const result); diff --git a/include/xrpl/tx/Transactor.h b/include/xrpl/tx/Transactor.h index bc5e8c80e7..a71285f70e 100644 --- a/include/xrpl/tx/Transactor.h +++ b/include/xrpl/tx/Transactor.h @@ -10,8 +10,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -29,7 +31,9 @@ namespace xrpl { -/** State information when preflighting a tx. */ +/** + * State information when preflighting a tx. + */ struct PreflightContext { public: @@ -72,7 +76,9 @@ public: operator=(PreflightContext const&) = delete; }; -/** State information when determining if a tx is likely to claim a fee. */ +/** + * State information when determining if a tx is likely to claim a fee. + */ struct PreclaimContext { public: @@ -126,6 +132,21 @@ struct PreflightResult; // Needed for preflight specialization class Change; +enum class FeePayerType { + Account, + Delegate, + SponsorCoSigned, + SponsorPreFunded, +}; + +struct FeePayer +{ + AccountID id; + Keylet keylet; + SF_AMOUNT const& balanceField; + FeePayerType type{FeePayerType::Account}; +}; + class Transactor { protected: @@ -144,7 +165,9 @@ public: enum class ConsequencesFactoryType { Normal, Blocker, Custom }; - /** Process the transaction. */ + /** + * Process the transaction. + */ ApplyResult operator()(); @@ -160,16 +183,17 @@ public: return ctx_.view(); } - /** Check all invariants for the current transaction. + /** + * Check all invariants for the current transaction. * - * Runs transaction-specific invariants first (visitInvariantEntry + - * finalizeInvariants), then protocol-level invariants. Both layers - * always run; the worst failure code is returned. + * Runs transaction-specific invariants first (visitInvariantEntry + + * finalizeInvariants), then protocol-level invariants. Both layers + * always run; the worst failure code is returned. * - * @param result the tentative TER from transaction processing. - * @param fee the fee consumed by the transaction. + * @param result the tentative TER from transaction processing. + * @param fee the fee consumed by the transaction. * - * @return the final TER after all invariant checks. + * @return the final TER after all invariant checks. */ [[nodiscard]] TER checkInvariants(TER result, XRPAmount fee); @@ -298,6 +322,10 @@ public: return T::checkGranularSemantics(view, tx, heldGranularPermissions); } + + static NotTEC + checkSponsor(ReadView const& view, STTx const& tx); + ///////////////////////////////////////////////////// // Interface used by AccountDelete @@ -320,40 +348,42 @@ protected: virtual TER doApply() = 0; - /** Inspect a single ledger entry modified by this transaction. + /** + * Inspect a single ledger entry modified by this transaction. * - * Called once for every SLE created, modified, or deleted by the - * transaction, before finalizeInvariants. Implementations should - * accumulate whatever state they need to verify transaction-specific - * post-conditions. + * Called once for every SLE created, modified, or deleted by the + * transaction, before finalizeInvariants. Implementations should + * accumulate whatever state they need to verify transaction-specific + * post-conditions. * - * @param isDelete true if the entry was erased from the ledger. - * @param before the entry's state before the transaction (nullptr - * for newly created entries). - * @param after the entry's state as supplied by the apply logic - * for this transaction. For deletions, this is the - * SLE being erased and is not guaranteed to be null; - * callers must use isDelete rather than after == nullptr - * to detect deletions. + * @param isDelete true if the entry was erased from the ledger. + * @param before the entry's state before the transaction (nullptr + * for newly created entries). + * @param after the entry's state as supplied by the apply logic + * for this transaction. For deletions, this is the + * SLE being erased and is not guaranteed to be null; + * callers must use isDelete rather than after == nullptr + * to detect deletions. */ virtual void visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) = 0; - /** Check transaction-specific post-conditions after all entries have - * been visited. + /** + * Check transaction-specific post-conditions after all entries have + * been visited. * - * Called once after every modified ledger entry has been passed to - * visitInvariantEntry. Returns true if all transaction-specific - * invariants hold, or false to fail the transaction with - * tecINVARIANT_FAILED. + * Called once after every modified ledger entry has been passed to + * visitInvariantEntry. Returns true if all transaction-specific + * invariants hold, or false to fail the transaction with + * tecINVARIANT_FAILED. * - * @param tx the transaction being applied. - * @param result the tentative TER result so far. - * @param fee the fee consumed by the transaction. - * @param view read-only view of the ledger after the transaction. - * @param j journal for logging invariant failures. + * @param tx the transaction being applied. + * @param result the tentative TER result so far. + * @param fee the fee consumed by the transaction. + * @param view read-only view of the ledger after the transaction. + * @param j journal for logging invariant failures. * - * @return true if all invariants pass; false otherwise. + * @return true if all invariants pass; false otherwise. */ [[nodiscard]] virtual bool finalizeInvariants( @@ -363,14 +393,15 @@ protected: ReadView const& view, beast::Journal const& j) = 0; - /** Compute the minimum fee required to process a transaction - with a given baseFee based on the current server load. - - @param registry The service registry. - @param baseFee The base fee of a candidate transaction - @see xrpl::calculateBaseFee - @param fees Fee settings from the current ledger - @param flags Transaction processing fees + /** + * Compute the minimum fee required to process a transaction + * with a given baseFee based on the current server load. + * + * @param registry The service registry. + * @param baseFee The base fee of a candidate transaction + * @see xrpl::calculateBaseFee + * @param fees Fee settings from the current ledger + * @param flags Transaction processing fees */ static XRPAmount minimumFee(ServiceRegistry& registry, XRPAmount baseFee, Fees const& fees, ApplyFlags flags); @@ -419,12 +450,16 @@ protected: unit::ValueUnit max, unit::ValueUnit min = unit::ValueUnit{}); - /// Minimum will usually be zero. + /** + * Minimum will usually be zero. + */ template static bool validNumericMinimum(std::optional value, T min = T{}); - /// Minimum will usually be zero. + /** + * Minimum will usually be zero. + */ template static bool validNumericMinimum( @@ -459,6 +494,9 @@ private: std::pair reset(XRPAmount fee); + static FeePayer + getFeePayer(ReadView const& view, STTx const& tx); + TER consumeSeqProxy(SLE::pointer const& sleAccount); TER @@ -469,44 +507,48 @@ private: void trapTransaction(uint256) const; - /** Performs early sanity checks on the account and fee fields. - - (And passes flagMask to preflight0) - - Do not try to call preflight1 from preflight() in derived classes. See - the description of invokePreflight for details. - */ + /** + * Performs early sanity checks on the account and fee fields. + * + * (And passes flagMask to preflight0) + * + * Do not try to call preflight1 from preflight() in derived classes. See + * the description of invokePreflight for details. + */ static NotTEC preflight1(PreflightContext const& ctx, std::uint32_t flagMask); - /** Checks whether the signature appears valid - - Do not try to call preflight2 from preflight() in derived classes. See - the description of invokePreflight for details. - */ + /** + * Checks whether the signature appears valid + * + * Do not try to call preflight2 from preflight() in derived classes. See + * the description of invokePreflight for details. + */ static NotTEC preflight2(PreflightContext const& ctx); - /** Universal validations - - Valid MPTAmount and XRPAmount - - Do not try to call preflightUniversal from preflight() in derived classes. See - the description of invokePreflight for details. - */ + /** + * Universal validations + * - Valid MPTAmount and XRPAmount + * + * Do not try to call preflightUniversal from preflight() in derived classes. See + * the description of invokePreflight for details. + */ static NotTEC preflightUniversal(PreflightContext const& ctx); - /** Check transaction-specific invariants only. + /** + * Check transaction-specific invariants only. * - * Walks every modified ledger entry via visitInvariantEntry, then - * calls finalizeInvariants on the derived transactor. Returns - * tecINVARIANT_FAILED if any transaction invariant is violated. + * Walks every modified ledger entry via visitInvariantEntry, then + * calls finalizeInvariants on the derived transactor. Returns + * tecINVARIANT_FAILED if any transaction invariant is violated. * - * @param result the tentative TER from transaction processing. - * @param fee the fee consumed by the transaction. + * @param result the tentative TER from transaction processing. + * @param fee the fee consumed by the transaction. * - * @return the original result if all invariants pass, or - * tecINVARIANT_FAILED otherwise. + * @return the original result if all invariants pass, or + * tecINVARIANT_FAILED otherwise. */ [[nodiscard]] TER checkTransactionInvariants(TER result, XRPAmount fee); @@ -518,20 +560,24 @@ Transactor::checkExtraFeatures(PreflightContext const& ctx) return true; } -/** Performs early sanity checks on the txid and flags */ +/** + * Performs early sanity checks on the txid and flags + */ NotTEC preflight0(PreflightContext const& ctx, std::uint32_t flagMask); namespace detail { -/** Checks the validity of the transactor signing key. +/** + * Checks the validity of the transactor signing key. * * Normally called from preflight1 with ctx.tx. */ NotTEC preflightCheckSigningKey(STObject const& sigObject, beast::Journal j); -/** Checks the special signing key state needed for simulation +/** + * Checks the special signing key state needed for simulation * * Normally called from preflight2 with ctx.tx. */ diff --git a/include/xrpl/tx/apply.h b/include/xrpl/tx/apply.h index 55d365c31b..8c11d870f1 100644 --- a/include/xrpl/tx/apply.h +++ b/include/xrpl/tx/apply.h @@ -16,88 +16,98 @@ namespace xrpl { class HashRouter; class ServiceRegistry; -/** Describes the pre-processing validity of a transaction. - - @see checkValidity, forceValidity -*/ +/** + * Describes the pre-processing validity of a transaction. + * + * @see checkValidity, forceValidity + */ enum class Validity { - /// Signature is bad. Didn't do local checks. + /** + * Signature is bad. Didn't do local checks. + */ SigBad, - /// Signature is good, but local checks fail. + /** + * Signature is good, but local checks fail. + */ SigGoodOnly, - /// Signature and local checks are good / passed. + /** + * Signature and local checks are good / passed. + */ Valid }; -/** Checks transaction signature and local checks. - - @return A `Validity` enum representing how valid the - `STTx` is and, if not `Valid`, a reason string. - - @note Results are cached internally, so tests will not be - repeated over repeated calls, unless cache expires. - - @return `std::pair`, where `.first` is the status, and - `.second` is the reason if appropriate. - - @see Validity -*/ +/** + * Checks transaction signature and local checks. + * + * @return A `Validity` enum representing how valid the + * `STTx` is and, if not `Valid`, a reason string. + * + * @note Results are cached internally, so tests will not be + * repeated over repeated calls, unless cache expires. + * + * @return `std::pair`, where `.first` is the status, and + * `.second` is the reason if appropriate. + * + * @see Validity + */ std::pair checkValidity(HashRouter& router, STTx const& tx, Rules const& rules); -/** Sets the validity of a given transaction in the cache. - - @warning Use with extreme care. - - @note Can only raise the validity to a more valid state, - and can not override anything cached bad. - - @see checkValidity, Validity -*/ +/** + * Sets the validity of a given transaction in the cache. + * + * @warning Use with extreme care. + * + * @note Can only raise the validity to a more valid state, + * and can not override anything cached bad. + * + * @see checkValidity, Validity + */ void forceValidity(HashRouter& router, uint256 const& txid, Validity validity); -/** Apply a transaction to an `OpenView`. - - This function is the canonical way to apply a transaction - to a ledger. It rolls the validation and application - steps into one function. To do the steps manually, the - correct calling order is: - @code{.cpp} - preflight -> preclaim -> doApply - @endcode - The result of one function must be passed to the next. - The `preflight` result can be safely cached and reused - asynchronously, but `preclaim` and `doApply` must be called - in the same thread and with the same view. - - @note Does not throw. - - For open ledgers, the `Transactor` will catch exceptions - and return `tefEXCEPTION`. For closed ledgers, the - `Transactor` will attempt to only charge a fee, - and return `tecFAILED_PROCESSING`. - - If the `Transactor` gets an exception while trying - to charge the fee, it will be caught and - turned into `tefEXCEPTION`. - - For network health, a `Transactor` makes its - best effort to at least charge a fee if the - ledger is closed. - - @param app The current running `Application`. - @param view The open ledger that the transaction - will attempt to be applied to. - @param tx The transaction to be checked. - @param flags `ApplyFlags` describing processing options. - @param journal A journal. - - @see preflight, preclaim, doApply - - @return A pair with the `TER` and a `bool` indicating - whether or not the transaction was applied. -*/ +/** + * Apply a transaction to an `OpenView`. + * + * This function is the canonical way to apply a transaction + * to a ledger. It rolls the validation and application + * steps into one function. To do the steps manually, the + * correct calling order is: + * @code + * preflight -> preclaim -> doApply + * @endcode + * The result of one function must be passed to the next. + * The `preflight` result can be safely cached and reused + * asynchronously, but `preclaim` and `doApply` must be called + * in the same thread and with the same view. + * + * @note Does not throw. + * + * For open ledgers, the `Transactor` will catch exceptions + * and return `tefEXCEPTION`. For closed ledgers, the + * `Transactor` will attempt to only charge a fee, + * and return `tecFAILED_PROCESSING`. + * + * If the `Transactor` gets an exception while trying + * to charge the fee, it will be caught and + * turned into `tefEXCEPTION`. + * + * For network health, a `Transactor` makes its + * best effort to at least charge a fee if the + * ledger is closed. + * + * @param app The current running `Application`. + * @param view The open ledger that the transaction + * will attempt to be applied to. + * @param tx The transaction to be checked. + * @param flags `ApplyFlags` describing processing options. + * @param journal A journal. + * + * @see preflight, preclaim, doApply + * + * @return A pair with the `TER` and a `bool` indicating + * whether or not the transaction was applied. + */ ApplyResult apply( ServiceRegistry& registry, @@ -106,26 +116,34 @@ apply( ApplyFlags flags, beast::Journal journal); -/** Enum class for return value from `applyTransaction` - - @see applyTransaction -*/ +/** + * Enum class for return value from `applyTransaction` + * + * @see applyTransaction + */ enum class ApplyTransactionResult { - /// Applied to this ledger + /** + * Applied to this ledger + */ Success, - /// Should not be retried in this ledger + /** + * Should not be retried in this ledger + */ Fail, - /// Should be retried in this ledger + /** + * Should be retried in this ledger + */ Retry }; -/** Transaction application helper - - Provides more detailed logging and decodes the - correct behavior based on the `TER` type - - @see ApplyTransactionResult -*/ +/** + * Transaction application helper + * + * Provides more detailed logging and decodes the + * correct behavior based on the `TER` type + * + * @see ApplyTransactionResult + */ ApplyTransactionResult applyTransaction( ServiceRegistry& registry, diff --git a/include/xrpl/tx/applySteps.h b/include/xrpl/tx/applySteps.h index 3298e49192..bd495481f2 100644 --- a/include/xrpl/tx/applySteps.h +++ b/include/xrpl/tx/applySteps.h @@ -33,8 +33,9 @@ struct ApplyResult } }; -/** Return true if the transaction can claim a fee (tec), - and the `ApplyFlags` do not allow soft failures. +/** + * Return true if the transaction can claim a fee (tec), + * and the `ApplyFlags` do not allow soft failures. */ inline bool isTecClaimHardFail(TER ter, ApplyFlags flags) @@ -42,34 +43,51 @@ isTecClaimHardFail(TER ter, ApplyFlags flags) return isTecClaim(ter) && ((flags & TapRetry) == 0u); } -/** Class describing the consequences to the account - of applying a transaction if the transaction consumes - the maximum XRP allowed. -*/ +/** + * Class describing the consequences to the account + * of applying a transaction if the transaction consumes + * the maximum XRP allowed. + */ class TxConsequences { public: - /// Describes how the transaction affects subsequent - /// transactions + /** + * Describes how the transaction affects subsequent + * transactions + */ enum class Category { - /// Moves currency around, creates offers, etc. + /** + * Moves currency around, creates offers, etc. + */ Normal = 0, - /// Affects the ability of subsequent transactions - /// to claim a fee. Eg. `SetRegularKey` + /** + * Affects the ability of subsequent transactions + * to claim a fee. Eg. `SetRegularKey` + */ Blocker }; private: - /// Describes how the transaction affects subsequent - /// transactions + /** + * Describes how the transaction affects subsequent + * transactions + */ bool isBlocker_; - /// Transaction fee + /** + * Transaction fee + */ XRPAmount fee_; - /// Does NOT include the fee. + /** + * Does NOT include the fee. + */ XRPAmount potentialSpend_; - /// SeqProxy of transaction. + /** + * SeqProxy of transaction. + */ SeqProxy seqProx_; - /// Number of sequences consumed. + /** + * Number of sequences consumed. + */ std::uint32_t sequencesConsumed_; public: @@ -77,58 +95,84 @@ public: // Asserts if tesSUCCESS is passed. explicit TxConsequences(NotTEC pfResult); - /// Constructor if the STTx has no notable consequences for the TxQ. + /** + * Constructor if the STTx has no notable consequences for the TxQ. + */ explicit TxConsequences(STTx const& tx); - /// Constructor for a blocker. + /** + * Constructor for a blocker. + */ TxConsequences(STTx const& tx, Category category); - /// Constructor for an STTx that may consume more XRP than the fee. + /** + * Constructor for an STTx that may consume more XRP than the fee. + */ TxConsequences(STTx const& tx, XRPAmount potentialSpend); - /// Constructor for an STTx that consumes more than the usual sequences. + /** + * Constructor for an STTx that consumes more than the usual sequences. + */ TxConsequences(STTx const& tx, std::uint32_t sequencesConsumed); - /// Copy constructor + /** + * Copy constructor + */ TxConsequences(TxConsequences const&) = default; - /// Copy assignment operator + /** + * Copy assignment operator + */ TxConsequences& operator=(TxConsequences const&) = default; - /// Move constructor + /** + * Move constructor + */ TxConsequences(TxConsequences&&) = default; - /// Move assignment operator + /** + * Move assignment operator + */ TxConsequences& operator=(TxConsequences&&) = default; - /// Fee + /** + * Fee + */ [[nodiscard]] XRPAmount fee() const { return fee_; } - /// Potential Spend + /** + * Potential Spend + */ [[nodiscard]] XRPAmount const& potentialSpend() const { return potentialSpend_; } - /// SeqProxy + /** + * SeqProxy + */ [[nodiscard]] SeqProxy seqProxy() const { return seqProx_; } - /// Sequences consumed + /** + * Sequences consumed + */ [[nodiscard]] std::uint32_t sequencesConsumed() const { return sequencesConsumed_; } - /// Returns true if the transaction is a blocker. + /** + * Returns true if the transaction is a blocker. + */ [[nodiscard]] bool isBlocker() const { @@ -145,32 +189,49 @@ public: } }; -/** Describes the results of the `preflight` check - - @note All members are const to make it more difficult - to "fake" a result without calling `preflight`. - @see preflight, preclaim, doApply, apply -*/ +/** + * Describes the results of the `preflight` check + * + * @note All members are const to make it more difficult + * to "fake" a result without calling `preflight`. + * @see preflight, preclaim, doApply, apply + */ struct PreflightResult { public: - /// From the input - the transaction + /** + * From the input - the transaction + */ STTx const& tx; - /// From the input - the batch identifier, if part of a batch + /** + * From the input - the batch identifier, if part of a batch + */ std::optional const parentBatchId; - /// From the input - the rules + /** + * From the input - the rules + */ Rules const rules; - /// Consequences of the transaction + /** + * Consequences of the transaction + */ TxConsequences const consequences; - /// From the input - the flags + /** + * From the input - the flags + */ ApplyFlags const flags; - /// From the input - the journal + /** + * From the input - the journal + */ beast::Journal const j; - /// Intermediate transaction result + /** + * Intermediate transaction result + */ NotTEC const ter; - /// Constructor + /** + * Constructor + */ template PreflightResult(Context const& ctx, std::pair const& result) : tx(ctx.tx) @@ -184,39 +245,58 @@ public: } PreflightResult(PreflightResult const&) = default; - /// Deleted copy assignment operator + /** + * Deleted copy assignment operator + */ PreflightResult& operator=(PreflightResult const&) = delete; }; -/** Describes the results of the `preclaim` check - - @note All members are const to make it more difficult - to "fake" a result without calling `preclaim`. - @see preflight, preclaim, doApply, apply -*/ +/** + * Describes the results of the `preclaim` check + * + * @note All members are const to make it more difficult + * to "fake" a result without calling `preclaim`. + * @see preflight, preclaim, doApply, apply + */ struct PreclaimResult { public: - /// From the input - the ledger view + /** + * From the input - the ledger view + */ ReadView const& view; - /// From the input - the transaction + /** + * From the input - the transaction + */ STTx const& tx; - /// From the input - the batch identifier, if part of a batch + /** + * From the input - the batch identifier, if part of a batch + */ std::optional const parentBatchId; - /// From the input - the flags + /** + * From the input - the flags + */ ApplyFlags const flags; - /// From the input - the journal + /** + * From the input - the journal + */ beast::Journal const j; - /// Intermediate transaction result + /** + * Intermediate transaction result + */ TER const ter; - /// Success flag - whether the transaction is likely to - /// claim a fee + /** + * Success flag - whether the transaction is likely to + * claim a fee + */ bool const likelyToClaimFee{}; - /// Constructor + /** + * Constructor + */ template PreclaimResult(Context const& ctx, TER ter) : view(ctx.view) @@ -230,27 +310,30 @@ public: } PreclaimResult(PreclaimResult const&) = default; - /// Deleted copy assignment operator + /** + * Deleted copy assignment operator + */ PreclaimResult& operator=(PreclaimResult const&) = delete; }; -/** Gate a transaction based on static information. - - The transaction is checked against all possible - validity constraints that do not require a ledger. - - @param app The current running `Application`. - @param rules The `Rules` in effect at the time of the check. - @param tx The transaction to be checked. - @param flags `ApplyFlags` describing processing options. - @param j A journal. - - @see PreflightResult, preclaim, doApply, apply - - @return A `PreflightResult` object containing, among - other things, the `TER` code. -*/ +/** + * Gate a transaction based on static information. + * + * The transaction is checked against all possible + * validity constraints that do not require a ledger. + * + * @param app The current running `Application`. + * @param rules The `Rules` in effect at the time of the check. + * @param tx The transaction to be checked. + * @param flags `ApplyFlags` describing processing options. + * @param j A journal. + * + * @see PreflightResult, preclaim, doApply, apply + * + * @return A `PreflightResult` object containing, among + * other things, the `TER` code. + */ /** @{ */ PreflightResult preflight( @@ -270,86 +353,90 @@ preflight( beast::Journal j); /** @} */ -/** Gate a transaction based on static ledger information. - - The transaction is checked against all possible - validity constraints that DO require a ledger. - - If preclaim succeeds, then the transaction is very - likely to claim a fee. This will determine if the - transaction is safe to relay without being applied - to the open ledger. - - "Succeeds" in this case is defined as returning a - `tes` or `tec`, since both lead to claiming a fee. - - @pre The transaction has been checked - and validated using `preflight` - - @param preflightResult The result of a previous - call to `preflight` for the transaction. - @param app The current running `Application`. - @param view The open ledger that the transaction - will attempt to be applied to. - - @see PreclaimResult, preflight, doApply, apply - - @return A `PreclaimResult` object containing, among - other things the `TER` code and the base fee value for - this transaction. -*/ +/** + * Gate a transaction based on static ledger information. + * + * The transaction is checked against all possible + * validity constraints that DO require a ledger. + * + * If preclaim succeeds, then the transaction is very + * likely to claim a fee. This will determine if the + * transaction is safe to relay without being applied + * to the open ledger. + * + * "Succeeds" in this case is defined as returning a + * `tes` or `tec`, since both lead to claiming a fee. + * + * @pre The transaction has been checked + * and validated using `preflight` + * + * @param preflightResult The result of a previous + * call to `preflight` for the transaction. + * @param app The current running `Application`. + * @param view The open ledger that the transaction + * will attempt to be applied to. + * + * @see PreclaimResult, preflight, doApply, apply + * + * @return A `PreclaimResult` object containing, among + * other things the `TER` code and the base fee value for + * this transaction. + */ PreclaimResult preclaim(PreflightResult const& preflightResult, ServiceRegistry& registry, OpenView const& view); -/** Compute only the expected base fee for a transaction. - - Base fees are transaction specific, so any calculation - needing them must get the base fee for each transaction. - - No validation is done or implied by this function. - - Caller is responsible for handling any exceptions. - Since none should be thrown, that will usually - mean terminating. - - @param view The current open ledger. - @param tx The transaction to be checked. - - @return The base fee. -*/ +/** + * Compute only the expected base fee for a transaction. + * + * Base fees are transaction specific, so any calculation + * needing them must get the base fee for each transaction. + * + * No validation is done or implied by this function. + * + * Caller is responsible for handling any exceptions. + * Since none should be thrown, that will usually + * mean terminating. + * + * @param view The current open ledger. + * @param tx The transaction to be checked. + * + * @return The base fee. + */ XRPAmount calculateBaseFee(ReadView const& view, STTx const& tx); -/** Return the minimum fee that an "ordinary" transaction would pay. - - When computing the FeeLevel for a transaction the TxQ sometimes needs - the know what an "ordinary" or reference transaction would be required - to pay. - - @param view The current open ledger. - @param tx The transaction so the correct multisigner count is used. - - @return The base fee in XRPAmount. -*/ +/** + * Return the minimum fee that an "ordinary" transaction would pay. + * + * When computing the FeeLevel for a transaction the TxQ sometimes needs + * the know what an "ordinary" or reference transaction would be required + * to pay. + * + * @param view The current open ledger. + * @param tx The transaction so the correct multisigner count is used. + * + * @return The base fee in XRPAmount. + */ XRPAmount calculateDefaultBaseFee(ReadView const& view, STTx const& tx); -/** Apply a prechecked transaction to an OpenView. - - @pre The transaction has been checked - and validated using `preflight` and `preclaim` - - @param preclaimResult The result of a previous - call to `preclaim` for the transaction. - @param registry The service registry. - @param view The open ledger that the transaction - will attempt to be applied to. - - @see preflight, preclaim, apply - - @return A pair with the `TER` and a `bool` indicating - whether or not the transaction was applied. -*/ +/** + * Apply a prechecked transaction to an OpenView. + * + * @pre The transaction has been checked + * and validated using `preflight` and `preclaim` + * + * @param preclaimResult The result of a previous + * call to `preclaim` for the transaction. + * @param registry The service registry. + * @param view The open ledger that the transaction + * will attempt to be applied to. + * + * @see preflight, preclaim, apply + * + * @return A pair with the `TER` and a `bool` indicating + * whether or not the transaction was applied. + */ ApplyResult doApply(PreclaimResult const& preclaimResult, ServiceRegistry& registry, OpenView& view); diff --git a/include/xrpl/tx/invariants/InvariantCheck.h b/include/xrpl/tx/invariants/InvariantCheck.h index d19f34635d..bb51105de2 100644 --- a/include/xrpl/tx/invariants/InvariantCheck.h +++ b/include/xrpl/tx/invariants/InvariantCheck.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -375,7 +376,8 @@ public: finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); }; -/** Verify that MPT/XRP STAmounts are canonical in any ledger entries left after the +/** + * Verify that MPT/XRP STAmounts are canonical in any ledger entries left after the * transaction applies. */ class ValidAmounts @@ -441,7 +443,9 @@ using InvariantChecks = std::tuple< ValidMPTPayment, ValidAmounts, ValidMPTTransfer, - ObjectHasPseudoAccount>; + ObjectHasPseudoAccount, + SponsorshipOwnerCountsMatch, + SponsorshipAccountCountMatchesField>; /** * @brief get a tuple of all invariant checks diff --git a/include/xrpl/tx/invariants/MPTInvariant.h b/include/xrpl/tx/invariants/MPTInvariant.h index 1d39fd13d8..9a546fa400 100644 --- a/include/xrpl/tx/invariants/MPTInvariant.h +++ b/include/xrpl/tx/invariants/MPTInvariant.h @@ -31,16 +31,22 @@ class ValidMPTIssuance // MPToken by an issuer bool mptCreatedByIssuer_ = false; - /// sfReferenceHolding is intended to be set exactly once at vault - /// creation and immutable thereafter; true when that rule was violated. + /** + * sfReferenceHolding is intended to be set exactly once at vault + * creation and immutable thereafter; true when that rule was violated. + */ bool referenceHoldingSetOnCreate_ = false; - /// True when sfReferenceHolding was mutated on an existing MPTokenIssuance. + /** + * True when sfReferenceHolding was mutated on an existing MPTokenIssuance. + */ bool referenceHoldingMutated_ = false; - /// MPTokens and RippleStates deleted during apply. finalize() checks each - /// holder's AccountRoot to detect vault pseudo-account holdings deleted - /// outside VaultDelete. All these checks are gated on fixCleanup3_2_0. + /** + * MPTokens and RippleStates deleted during apply. finalize() checks each + * holder's AccountRoot to detect vault pseudo-account holdings deleted + * outside VaultDelete. All these checks are gated on fixCleanup3_2_0. + */ std::vector> deletedHoldings_; public: diff --git a/include/xrpl/tx/invariants/SponsorshipInvariant.h b/include/xrpl/tx/invariants/SponsorshipInvariant.h new file mode 100644 index 0000000000..e664685c16 --- /dev/null +++ b/include/xrpl/tx/invariants/SponsorshipInvariant.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +namespace xrpl { + +/** + * @brief Invariant: Sponsored owner counts are balanced. + * + * The following checks are made for every transaction: + * - The sum of all per-account deltas of `sfSponsoredOwnerCount` equals + * the sum of all per-account deltas of `sfSponsoringOwnerCount`. + * - Account OwnerCount must be greater than or equal to SponsoredOwnerCount. + * - The net delta of sponsored object owner counts (the owner-count + * magnitude of sponsored ledger entries) equals the net delta of + * `sfSponsoredOwnerCount`. + */ +class SponsorshipOwnerCountsMatch +{ + std::int64_t deltaSponsoredOwnerCount_ = 0; + std::int64_t deltaSponsoringOwnerCount_ = 0; + std::int64_t deltaSponsoredObjectOwnerCount_ = 0; + std::uint64_t ownerCountBelowSponsored_ = 0; + +public: + void + visitEntry(bool, SLE::const_ref, SLE::const_ref); + + [[nodiscard]] bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const; +}; + +/** + * @brief Invariant: Sponsoring account relationships tracked consistently. + * + * The following check is made for every transaction: + * - The net delta of `sfSponsoringAccountCount` across all accounts equals + * the net delta of the count of ltACCOUNT_ROOT entries having + * `sfSponsor` present (presence transitions only: add/remove). + */ +class SponsorshipAccountCountMatchesField +{ + std::int64_t deltaSponsoringAccountCount_ = 0; + std::int64_t deltaSponsorFieldPresence_ = 0; + +public: + void + visitEntry(bool, SLE::const_ref, SLE::const_ref); + + [[nodiscard]] bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const; +}; + +} // namespace xrpl diff --git a/include/xrpl/tx/invariants/VaultInvariant.h b/include/xrpl/tx/invariants/VaultInvariant.h index bc8246b234..136c6c4a25 100644 --- a/include/xrpl/tx/invariants/VaultInvariant.h +++ b/include/xrpl/tx/invariants/VaultInvariant.h @@ -21,7 +21,7 @@ namespace xrpl { -/* +/** * @brief Invariants: Vault object and MPTokenIssuance for vault shares * * - vault deleted and vault created is empty @@ -96,7 +96,7 @@ private: * * @param vaultDelta Delta of the vault's asset balance for this transaction. * @param rules Active ledger rules (used to check the amendment). - * @returns The minimum scale to apply when rounding vault-related amounts. + * @return The minimum scale to apply when rounding vault-related amounts. */ [[nodiscard]] std::int32_t computeVaultMinScale(DeltaInfo const& vaultDelta, Rules const& rules) const; @@ -109,7 +109,7 @@ private: * to the vault asset held by @p id. * * @param id Account whose asset delta is requested. - * @returns The delta, or @c std::nullopt if the entry was not touched. + * @return The delta, or @c std::nullopt if the entry was not touched. */ [[nodiscard]] std::optional deltaAssets(AccountID const& id) const; @@ -124,8 +124,8 @@ private: * * @param tx The transaction being applied. * @param fee Fee charged by this transaction. - * @returns The fee-adjusted delta, or @c std::nullopt if the net delta is - * zero or the account entry was not touched. + * @return The fee-adjusted delta, or @c std::nullopt if the net delta is + * zero or the account entry was not touched. */ [[nodiscard]] std::optional deltaAssetsTxAccount(STTx const& tx, XRPAmount fee) const; @@ -138,7 +138,7 @@ private: * returned. * * @param id Account whose share delta is requested. - * @returns The delta, or @c std::nullopt if the entry was not touched. + * @return The delta, or @c std::nullopt if the entry was not touched. */ [[nodiscard]] std::optional deltaShares(AccountID const& id) const; @@ -147,8 +147,8 @@ private: * @brief Check whether a vault holds no assets. * * @param vault Snapshot of the vault to test. - * @returns @c true when both @c assetsAvailable and @c assetsTotal are - * zero. + * @return @c true when both @c assetsAvailable and @c assetsTotal are + * zero. */ [[nodiscard]] static bool isVaultEmpty(Vault const& vault); diff --git a/include/xrpl/tx/paths/AMMLiquidity.h b/include/xrpl/tx/paths/AMMLiquidity.h index bf62155b61..1904445554 100644 --- a/include/xrpl/tx/paths/AMMLiquidity.h +++ b/include/xrpl/tx/paths/AMMLiquidity.h @@ -17,7 +17,8 @@ namespace xrpl { template class AMMOffer; -/** AMMLiquidity class provides AMM offers to BookStep class. +/** + * AMMLiquidity class provides AMM offers to BookStep class. * The offers are generated in two ways. If there are multiple * paths specified to the payment transaction then the offers * are generated based on the Fibonacci sequence with @@ -59,7 +60,8 @@ public: AMMLiquidity& operator=(AMMLiquidity const&) = delete; - /** Generate AMM offer. Returns nullopt if clobQuality is provided + /** + * Generate AMM offer. Returns nullopt if clobQuality is provided * and it is better than AMM offer quality. Otherwise returns AMM offer. * If clobQuality is provided then AMM offer size is set based on the * quality. @@ -104,12 +106,14 @@ public: } private: - /** Fetches current AMM balances. + /** + * Fetches current AMM balances. */ [[nodiscard]] TAmounts fetchBalances(ReadView const& view) const; - /** Generate AMM offers with the offer size based on Fibonacci sequence. + /** + * Generate AMM offers with the offer size based on Fibonacci sequence. * The sequence corresponds to the payment engine iterations with AMM * liquidity. Iterations that don't consume AMM offers don't count. * The number of iterations with AMM offers is limited. @@ -119,7 +123,8 @@ private: [[nodiscard]] TAmounts generateFibSeqOffer(TAmounts const& balances) const; - /** Generate max offer. + /** + * Generate max offer. * If `fixAMMOverflowOffer` is active, the offer is generated as: * takerGets = 99% * balances.out takerPays = swapOut(takerGets). * Return nullopt if takerGets is 0 or takerGets == balances.out. diff --git a/include/xrpl/tx/paths/AMMOffer.h b/include/xrpl/tx/paths/AMMOffer.h index 40a9cf40b3..8e7ffedc10 100644 --- a/include/xrpl/tx/paths/AMMOffer.h +++ b/include/xrpl/tx/paths/AMMOffer.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -19,7 +20,8 @@ template class AMMLiquidity; class QualityFunction; -/** Represents synthetic AMM offer in BookStep. AMMOffer mirrors TOffer +/** + * Represents synthetic AMM offer in BookStep. AMMOffer mirrors TOffer * methods for use in generic BookStep methods. AMMOffer amounts * are changed indirectly in BookStep limiting steps. */ @@ -86,14 +88,16 @@ public: return consumed_; } - /** Limit out of the provided offer. If one-path then swapOut + /** + * Limit out of the provided offer. If one-path then swapOut * using current balances. If multi-path then ceil_out using * current quality. */ [[nodiscard]] TAmounts limitOut(TAmounts const& offerAmount, TOut const& limit, bool roundUp) const; - /** Limit in of the provided offer. If one-path then swapIn + /** + * Limit in of the provided offer. If one-path then swapIn * using current balances. If multi-path then ceil_in using * current quality. */ @@ -103,14 +107,18 @@ public: [[nodiscard]] QualityFunction getQualityFunc() const; - /** Send funds without incurring the transfer fee + /** + * Send funds without incurring the transfer fee */ template static TER send(Args&&... args) { return accountSend( - std::forward(args)..., WaiveTransferFee::Yes, AllowMPTOverflow::Yes); + std::forward(args)..., + SLE::pointer(), + WaiveTransferFee::Yes, + AllowMPTOverflow::Yes); } [[nodiscard]] bool @@ -127,7 +135,8 @@ public: return {ofrInRate, QUALITY_ONE}; } - /** Check the new pool product is greater or equal to the old pool + /** + * Check the new pool product is greater or equal to the old pool * product or if decreases then within some threshold. */ [[nodiscard]] bool diff --git a/include/xrpl/tx/paths/BookTip.h b/include/xrpl/tx/paths/BookTip.h index bad007ca5b..0a7e3c343e 100644 --- a/include/xrpl/tx/paths/BookTip.h +++ b/include/xrpl/tx/paths/BookTip.h @@ -11,10 +11,11 @@ namespace xrpl { class Logs; -/** Iterates and consumes raw offers in an order book. - Offers are presented from highest quality to lowest quality. This will - return all offers present including missing, invalid, unfunded, etc. -*/ +/** + * Iterates and consumes raw offers in an order book. + * Offers are presented from highest quality to lowest quality. This will + * return all offers present including missing, invalid, unfunded, etc. + */ class BookTip { private: @@ -28,7 +29,9 @@ private: Quality quality_{}; public: - /** Create the iterator. */ + /** + * Create the iterator. + */ BookTip(ApplyView& view, Book const& book); [[nodiscard]] uint256 const& @@ -55,10 +58,11 @@ public: return entry_; } - /** Erases the current offer and advance to the next offer. - Complexity: Constant - @return `true` if there is a next offer - */ + /** + * Erases the current offer and advance to the next offer. + * Complexity: Constant + * @return `true` if there is a next offer + */ bool step(beast::Journal j); }; diff --git a/include/xrpl/tx/paths/Flow.h b/include/xrpl/tx/paths/Flow.h index f73b9a3440..af056ce2fe 100644 --- a/include/xrpl/tx/paths/Flow.h +++ b/include/xrpl/tx/paths/Flow.h @@ -18,25 +18,25 @@ struct FlowDebugInfo; } // namespace path::detail /** - Make a payment from the src account to the dst account - - @param view Trust lines and balances - @param deliver Amount to deliver to the dst account - @param src Account providing input funds for the payment - @param dst Account receiving the payment - @param paths Set of paths to explore for liquidity - @param defaultPaths Include defaultPaths in the path set - @param partialPayment If the payment cannot deliver the entire - requested amount, deliver as much as possible, given the constraints - @param ownerPaysTransferFee If true then owner, not sender, pays fee - @param offerCrossing If Yes or Sell then flow is executing offer crossing, not - payments - @param limitQuality Do not use liquidity below this quality threshold - @param sendMax Do not spend more than this amount - @param j Journal to write journal messages to - @param flowDebugInfo If non-null a pointer to FlowDebugInfo for debugging - @return Actual amount in and out, and the result code -*/ + * Make a payment from the src account to the dst account + * + * @param view Trust lines and balances + * @param deliver Amount to deliver to the dst account + * @param src Account providing input funds for the payment + * @param dst Account receiving the payment + * @param paths Set of paths to explore for liquidity + * @param defaultPaths Include defaultPaths in the path set + * @param partialPayment If the payment cannot deliver the entire + * requested amount, deliver as much as possible, given the constraints + * @param ownerPaysTransferFee If true then owner, not sender, pays fee + * @param offerCrossing If Yes or Sell then flow is executing offer crossing, not + * payments + * @param limitQuality Do not use liquidity below this quality threshold + * @param sendMax Do not spend more than this amount + * @param j Journal to write journal messages to + * @param flowDebugInfo If non-null a pointer to FlowDebugInfo for debugging + * @return Actual amount in and out, and the result code + */ path::RippleCalc::Output flow( PaymentSandbox& view, diff --git a/include/xrpl/tx/paths/Offer.h b/include/xrpl/tx/paths/Offer.h index 164a933ba1..7f368fc2dd 100644 --- a/include/xrpl/tx/paths/Offer.h +++ b/include/xrpl/tx/paths/Offer.h @@ -44,38 +44,44 @@ public: TOffer(SLE::pointer entry, Quality quality); - /** Returns the quality of the offer. - Conceptually, the quality is the ratio of output to input currency. - The implementation calculates it as the ratio of input to output - currency (so it sorts ascending). The quality is computed at the time - the offer is placed, and never changes for the lifetime of the offer. - This is an important business rule that maintains accuracy when an - offer is partially filled; Subsequent partial fills will use the - original quality. - */ + /** + * Returns the quality of the offer. + * Conceptually, the quality is the ratio of output to input currency. + * The implementation calculates it as the ratio of input to output + * currency (so it sorts ascending). The quality is computed at the time + * the offer is placed, and never changes for the lifetime of the offer. + * This is an important business rule that maintains accuracy when an + * offer is partially filled; Subsequent partial fills will use the + * original quality. + */ [[nodiscard]] Quality quality() const noexcept { return quality_; } - /** Returns the account id of the offer's owner. */ + /** + * Returns the account id of the offer's owner. + */ [[nodiscard]] AccountID const& owner() const { return accountID_; } - /** Returns the in and out amounts. - Some or all of the out amount may be unfunded. - */ + /** + * Returns the in and out amounts. + * Some or all of the out amount may be unfunded. + */ [[nodiscard]] TAmounts const& amount() const { return amounts_; } - /** Returns `true` if no more funds can flow through this offer. */ + /** + * Returns `true` if no more funds can flow through this offer. + */ [[nodiscard]] bool fullyConsumed() const { @@ -86,7 +92,9 @@ public: return false; } - /** Adjusts the offer to indicate that we consumed some (or all) of it. */ + /** + * Adjusts the offer to indicate that we consumed some (or all) of it. + */ void consume(ApplyView& view, TAmounts const& consumed) { @@ -142,7 +150,8 @@ public: return {ofrInRate, ofrOutRate}; } - /** Check any required invariant. Limit order book offer + /** + * Check any required invariant. Limit order book offer * always returns true. */ [[nodiscard]] bool @@ -234,7 +243,8 @@ template TER TOffer::send(Args&&... args) { - return accountSend(std::forward(args)..., WaiveTransferFee::No, AllowMPTOverflow::Yes); + return accountSend( + std::forward(args)..., SLE::pointer(), WaiveTransferFee::No, AllowMPTOverflow::Yes); } template diff --git a/include/xrpl/tx/paths/OfferStream.h b/include/xrpl/tx/paths/OfferStream.h index 98c58876c9..28eefb5d66 100644 --- a/include/xrpl/tx/paths/OfferStream.h +++ b/include/xrpl/tx/paths/OfferStream.h @@ -85,23 +85,26 @@ public: virtual ~TOfferStreamBase() = default; - /** Returns the offer at the tip of the order book. - Offers are always presented in decreasing quality. - Only valid if step() returned `true`. - */ + /** + * Returns the offer at the tip of the order book. + * Offers are always presented in decreasing quality. + * Only valid if step() returned `true`. + */ [[nodiscard]] TOffer& tip() const { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) return const_cast(this)->offer_; } - /** Advance to the next valid offer. - This automatically removes: - - Offers with missing ledger entries - - Offers found unfunded - - expired offers - @return `true` if there is a valid offer. - */ + /** + * Advance to the next valid offer. + * This automatically removes: + * - Offers with missing ledger entries + * - Offers found unfunded + * - expired offers + * @return `true` if there is a valid offer. + */ bool step(); @@ -113,23 +116,24 @@ public: } }; -/** Presents and consumes the offers in an order book. - - The `view_' ` `ApplyView` accumulates changes to the ledger. - The `cancelView_` is used to determine if an offer is found - unfunded or became unfunded. - The `permToRemove` collection identifies offers that should be - removed even if the strand associated with this OfferStream - is not applied. - - Certain invalid offers are added to the `permToRemove` collection: - - Offers with missing ledger entries - - Offers that expired - - Offers found unfunded: - An offer is found unfunded when the corresponding balance is zero - and the caller has not modified the balance. This is accomplished - by also looking up the balance in the cancel view. -*/ +/** + * Presents and consumes the offers in an order book. + * + * The `view_' ` `ApplyView` accumulates changes to the ledger. + * The `cancelView_` is used to determine if an offer is found + * unfunded or became unfunded. + * The `permToRemove` collection identifies offers that should be + * removed even if the strand associated with this OfferStream + * is not applied. + * + * Certain invalid offers are added to the `permToRemove` collection: + * - Offers with missing ledger entries + * - Offers that expired + * - Offers found unfunded: + * An offer is found unfunded when the corresponding balance is zero + * and the caller has not modified the balance. This is accomplished + * by also looking up the balance in the cancel view. + */ template class FlowOfferStream : public TOfferStreamBase { diff --git a/include/xrpl/tx/paths/RippleCalc.h b/include/xrpl/tx/paths/RippleCalc.h index 62c966d384..c3ee9165a6 100644 --- a/include/xrpl/tx/paths/RippleCalc.h +++ b/include/xrpl/tx/paths/RippleCalc.h @@ -20,11 +20,12 @@ namespace detail { struct FlowDebugInfo; } // namespace detail -/** RippleCalc calculates the quality of a payment path. - - Quality is the amount of input required to produce a given output along a - specified path - another name for this is exchange rate. -*/ +/** + * RippleCalc calculates the quality of a payment path. + * + * Quality is the amount of input required to produce a given output along a + * specified path - another name for this is exchange rate. + */ class RippleCalc { public: diff --git a/include/xrpl/tx/paths/detail/FlatSets.h b/include/xrpl/tx/paths/detail/FlatSets.h index c0fc8fa417..267c0499a2 100644 --- a/include/xrpl/tx/paths/detail/FlatSets.h +++ b/include/xrpl/tx/paths/detail/FlatSets.h @@ -4,11 +4,12 @@ namespace xrpl { -/** Given two flat sets dst and src, compute dst = dst union src - - @param dst set to store the resulting union, and also a source of elements - for the union - @param src second source of elements for the union +/** + * Given two flat sets dst and src, compute dst = dst union src + * + * @param dst set to store the resulting union, and also a source of elements + * for the union + * @param src second source of elements for the union */ template void diff --git a/include/xrpl/tx/paths/detail/Steps.h b/include/xrpl/tx/paths/detail/Steps.h index 7eb6b938a5..8ee37c026c 100644 --- a/include/xrpl/tx/paths/detail/Steps.h +++ b/include/xrpl/tx/paths/detail/Steps.h @@ -50,31 +50,31 @@ issues(DebtDirection dir) } /** - A step in a payment path - - There are five concrete step classes: - DirectStepI is an IOU step between accounts - BookStepII is an IOU/IOU offer book - BookStepIX is an IOU/XRP offer book - BookStepXI is an XRP/IOU offer book - XRPEndpointStep is the source or destination account for XRP - MPTEndpointStep is the source or destination account for MPT - - Amounts may be transformed through a step in either the forward or the - reverse direction. In the forward direction, the function `fwd` is used to - find the amount the step would output given an input amount. In the reverse - direction, the function `rev` is used to find the amount of input needed to - produce the desired output. - - Amounts are always transformed using liquidity with the same quality (quality - is the amount out/amount in). For example, a BookStep may use multiple offers - when executing `fwd` or `rev`, but all those offers will be from the same - quality directory. - - A step may not have enough liquidity to transform the entire requested - amount. Both `fwd` and `rev` return a pair of amounts (one for input amount, - one for output amount) that show how much of the requested amount the step - was actually able to use. + * A step in a payment path + * + * There are five concrete step classes: + * DirectStepI is an IOU step between accounts + * BookStepII is an IOU/IOU offer book + * BookStepIX is an IOU/XRP offer book + * BookStepXI is an XRP/IOU offer book + * XRPEndpointStep is the source or destination account for XRP + * MPTEndpointStep is the source or destination account for MPT + * + * Amounts may be transformed through a step in either the forward or the + * reverse direction. In the forward direction, the function `fwd` is used to + * find the amount the step would output given an input amount. In the reverse + * direction, the function `rev` is used to find the amount of input needed to + * produce the desired output. + * + * Amounts are always transformed using liquidity with the same quality (quality + * is the amount out/amount in). For example, a BookStep may use multiple offers + * when executing `fwd` or `rev`, but all those offers will be from the same + * quality directory. + * + * A step may not have enough liquidity to transform the entire requested + * amount. Both `fwd` and `rev` return a pair of amounts (one for input amount, + * one for output amount) that show how much of the requested amount the step + * was actually able to use. */ class Step { @@ -82,17 +82,17 @@ public: virtual ~Step() = default; /** - Find the amount we need to put into the step to get the requested out - subject to liquidity limits - - @param sb view with the strand's state of balances and offers - @param afView view the state of balances before the strand runs - this determines if an offer becomes unfunded or is found unfunded - @param ofrsToRm offers found unfunded or in an error state are added to - this collection - @param out requested step output - @return actual step input and output - */ + * Find the amount we need to put into the step to get the requested out + * subject to liquidity limits + * + * @param sb view with the strand's state of balances and offers + * @param afView view the state of balances before the strand runs + * this determines if an offer becomes unfunded or is found unfunded + * @param ofrsToRm offers found unfunded or in an error state are added to + * this collection + * @param out requested step output + * @return actual step input and output + */ virtual std::pair rev(PaymentSandbox& sb, ApplyView& afView, @@ -100,17 +100,17 @@ public: EitherAmount const& out) = 0; /** - Find the amount we get out of the step given the input - subject to liquidity limits - - @param sb view with the strand's state of balances and offers - @param afView view the state of balances before the strand runs - this determines if an offer becomes unfunded or is found unfunded - @param ofrsToRm offers found unfunded or in an error state are added to - this collection - @param in requested step input - @return actual step input and output - */ + * Find the amount we get out of the step given the input + * subject to liquidity limits + * + * @param sb view with the strand's state of balances and offers + * @param afView view the state of balances before the strand runs + * this determines if an offer becomes unfunded or is found unfunded + * @param ofrsToRm offers found unfunded or in an error state are added to + * this collection + * @param in requested step input + * @return actual step input and output + */ virtual std::pair fwd(PaymentSandbox& sb, ApplyView& afView, @@ -118,23 +118,23 @@ public: EitherAmount const& in) = 0; /** - Amount of currency computed coming into the Step the last time the - step ran in reverse. - */ + * Amount of currency computed coming into the Step the last time the + * step ran in reverse. + */ [[nodiscard]] virtual std::optional cachedIn() const = 0; /** - Amount of currency computed coming out of the Step the last time the - step ran in reverse. - */ + * Amount of currency computed coming out of the Step the last time the + * step ran in reverse. + */ [[nodiscard]] virtual std::optional cachedOut() const = 0; /** - If this step is DirectStepI (IOU->IOU direct step), return the src - account. This is needed for checkNoRipple. - */ + * If this step is DirectStepI (IOU->IOU direct step), return the src + * account. This is needed for checkNoRipple. + */ [[nodiscard]] virtual std::optional directStepSrcAcct() const { @@ -150,19 +150,19 @@ public: } /** - If this step is a DirectStepI and the src redeems to the dst, return - true, otherwise return false. If this step is a BookStep, return false if - the owner pays the transfer fee, otherwise return true. - - @param sb view with the strand's state of balances and offers - @param dir reverse -> called from rev(); forward -> called from fwd(). - */ + * If this step is a DirectStepI and the src redeems to the dst, return + * true, otherwise return false. If this step is a BookStep, return false if + * the owner pays the transfer fee, otherwise return true. + * + * @param sb view with the strand's state of balances and offers + * @param dir reverse -> called from rev(); forward -> called from fwd(). + */ [[nodiscard]] virtual DebtDirection debtDirection(ReadView const& sb, StrandDirection dir) const = 0; /** - If this step is a DirectStepI, return the quality in of the dst account. - */ + * If this step is a DirectStepI, return the quality in of the dst account. + */ [[nodiscard]] virtual std::uint32_t lineQualityIn(ReadView const&) const { @@ -170,22 +170,23 @@ public: } /** - Find an upper bound of quality for the step - - @param v view to query the ledger state from - @param prevStepDir Set to DebtDirection::redeems if the previous step redeems. - @return A pair. The first element is the upper bound of quality for the step, or std::nullopt - if the step is dry. The second element will be set to DebtDirection::redeems if this - steps redeems, DebtDirection:issues if this step issues. - @note It is an upper bound because offers on the books may be unfunded. If there is always a - funded offer at the tip of the book, then we could rename this `theoreticalQuality` - rather than `qualityUpperBound`. It could still differ from the actual quality, but - except for "dust" amounts, it should be a good estimate for the actual quality. - */ + * Find an upper bound of quality for the step + * + * @param v view to query the ledger state from + * @param prevStepDir Set to DebtDirection::redeems if the previous step redeems. + * @return A pair. The first element is the upper bound of quality for the step, or std::nullopt + * if the step is dry. The second element will be set to DebtDirection::redeems if this + * steps redeems, DebtDirection:issues if this step issues. + * @note It is an upper bound because offers on the books may be unfunded. If there is always a + * funded offer at the tip of the book, then we could rename this `theoreticalQuality` + * rather than `qualityUpperBound`. It could still differ from the actual quality, but + * except for "dust" amounts, it should be a good estimate for the actual quality. + */ [[nodiscard]] virtual std::pair, DebtDirection> qualityUpperBound(ReadView const& v, DebtDirection prevStepDir) const = 0; - /** Get QualityFunction. Used in one path optimization where + /** + * Get QualityFunction. Used in one path optimization where * the quality function is non-constant (has AMM) and there is * limitQuality. QualityFunction allows calculation of * required path output given requested limitQuality. @@ -195,12 +196,13 @@ public: [[nodiscard]] virtual std::pair, DebtDirection> getQualityFunc(ReadView const& v, DebtDirection prevStepDir) const; - /** Return the number of offers consumed or partially consumed the last time - the step ran, including expired and unfunded offers. - - N.B. This this not the total number offers consumed by this step for the - entire payment, it is only the number the last time it ran. Offers may - be partially consumed multiple times during a payment. + /** + * Return the number of offers consumed or partially consumed the last time + * the step ran, including expired and unfunded offers. + * + * N.B. This this not the total number offers consumed by this step for the + * entire payment, it is only the number the last time it ran. Offers may + * be partially consumed multiple times during a payment. */ [[nodiscard]] virtual std::uint32_t offersUsed() const @@ -209,8 +211,8 @@ public: } /** - If this step is a BookStep, return the book. - */ + * If this step is a BookStep, return the book. + */ [[nodiscard]] virtual std::optional bookStepBook() const { @@ -218,15 +220,15 @@ public: } /** - Check if amount is zero - */ + * Check if amount is zero + */ [[nodiscard]] virtual bool isZero(EitherAmount const& out) const = 0; /** - Return true if the step should be considered inactive. - A strand that has additional liquidity may be marked inactive if a step - has consumed too many offers. + * Return true if the step should be considered inactive. + * A strand that has additional liquidity may be marked inactive if a step + * has consumed too many offers. */ [[nodiscard]] virtual bool inactive() const @@ -235,55 +237,59 @@ public: } /** - Return true if Out of lhs == Out of rhs. - */ + * Return true if Out of lhs == Out of rhs. + */ [[nodiscard]] virtual bool equalOut(EitherAmount const& lhs, EitherAmount const& rhs) const = 0; /** - Return true if In of lhs == In of rhs. - */ + * Return true if In of lhs == In of rhs. + */ [[nodiscard]] virtual bool equalIn(EitherAmount const& lhs, EitherAmount const& rhs) const = 0; /** - Check that the step can correctly execute in the forward direction - - @param sb view with the strands state of balances and offers - @param afView view the state of balances before the strand runs - this determines if an offer becomes unfunded or is found unfunded - @param in requested step input - @return first element is true if step is valid, second element is out - amount - */ + * Check that the step can correctly execute in the forward direction + * + * @param sb view with the strands state of balances and offers + * @param afView view the state of balances before the strand runs + * this determines if an offer becomes unfunded or is found unfunded + * @param in requested step input + * @return first element is true if step is valid, second element is out + * amount + */ virtual std::pair validFwd(PaymentSandbox& sb, ApplyView& afView, EitherAmount const& in) = 0; - /** Return true if lhs == rhs. - - @param lhs Step to compare. - @param rhs Step to compare. - @return true if lhs == rhs. - */ + /** + * Return true if lhs == rhs. + * + * @param lhs Step to compare. + * @param rhs Step to compare. + * @return true if lhs == rhs. + */ friend bool operator==(Step const& lhs, Step const& rhs) { return lhs.equal(rhs); } - /** Return true if lhs != rhs. - - @param lhs Step to compare. - @param rhs Step to compare. - @return true if lhs != rhs. - */ + /** + * Return true if lhs != rhs. + * + * @param lhs Step to compare. + * @param rhs Step to compare. + * @return true if lhs != rhs. + */ friend bool operator!=(Step const& lhs, Step const& rhs) { return !(lhs == rhs); } - /** Streaming operator for a Step. */ + /** + * Streaming operator for a Step. + */ friend std::ostream& operator<<(std::ostream& stream, Step const& step) { @@ -308,7 +314,7 @@ Step::getQualityFunc(ReadView const& v, DebtDirection prevStepDir) const return {std::nullopt, res.second}; } -/// @cond INTERNAL +/** @cond INTERNAL */ using Strand = std::vector>; inline std::uint32_t @@ -322,9 +328,9 @@ offersUsed(Strand const& strand) } return r; } -/// @endcond +/** @endcond */ -/// @cond INTERNAL +/** @cond INTERNAL */ inline bool operator==(Strand const& lhs, Strand const& rhs) { @@ -337,21 +343,21 @@ operator==(Strand const& lhs, Strand const& rhs) } return true; } -/// @endcond +/** @endcond */ -/* - Normalize a path by inserting implied accounts and offers - - @param src Account that is sending assets - @param dst Account that is receiving assets - @param deliver Asset the dst account will receive - (if issuer of deliver == dst, then accept any issuer) - @param sendMax Optional asset to send. - @param path Liquidity sources to use for this strand of the payment. The path - contains an ordered collection of the offer books to use and - accounts to ripple through. - @return error code and normalized path -*/ +/** + * Normalize a path by inserting implied accounts and offers + * + * @param src Account that is sending assets + * @param dst Account that is receiving assets + * @param deliver Asset the dst account will receive + * (if issuer of deliver == dst, then accept any issuer) + * @param sendMax Optional asset to send. + * @param path Liquidity sources to use for this strand of the payment. The path + * contains an ordered collection of the offer books to use and + * accounts to ripple through. + * @return error code and normalized path + */ std::pair normalizePath( AccountID const& src, @@ -361,29 +367,29 @@ normalizePath( STPath const& path); /** - Create a Strand for the specified path - - @param sb view for trust lines, balances, and attributes like auth and freeze - @param src Account that is sending assets - @param dst Account that is receiving assets - @param deliver Asset the dst account will receive - (if issuer of deliver == dst, then accept any issuer) - @param limitQuality Offer crossing BookSteps use this value in an - optimization. If, during direct offer crossing, the - quality of the tip of the book drops below this value, - then evaluating the strand can stop. - @param sendMaxAsset Optional asset to send. - @param path Liquidity sources to use for this strand of the payment. The path - contains an ordered collection of the offer books to use and - accounts to ripple through. - @param ownerPaysTransferFee false -> charge sender; true -> charge offer - owner - @param offerCrossing false -> payment; true -> offer crossing - @param ammContext counts iterations with AMM offers - @param domainID the domain that order books will use - @param j Journal for logging messages - @return Error code and constructed Strand -*/ + * Create a Strand for the specified path + * + * @param sb view for trust lines, balances, and attributes like auth and freeze + * @param src Account that is sending assets + * @param dst Account that is receiving assets + * @param deliver Asset the dst account will receive + * (if issuer of deliver == dst, then accept any issuer) + * @param limitQuality Offer crossing BookSteps use this value in an + * optimization. If, during direct offer crossing, the + * quality of the tip of the book drops below this value, + * then evaluating the strand can stop. + * @param sendMaxAsset Optional asset to send. + * @param path Liquidity sources to use for this strand of the payment. The path + * contains an ordered collection of the offer books to use and + * accounts to ripple through. + * @param ownerPaysTransferFee false -> charge sender; true -> charge offer + * owner + * @param offerCrossing false -> payment; true -> offer crossing + * @param ammContext counts iterations with AMM offers + * @param domainID the domain that order books will use + * @param j Journal for logging messages + * @return Error code and constructed Strand + */ std::pair toStrand( ReadView const& sb, @@ -400,31 +406,31 @@ toStrand( beast::Journal j); /** - Create a Strand for each specified path (including the default path, if - indicated) - - @param sb View for trust lines, balances, and attributes like auth and freeze - @param src Account that is sending assets - @param dst Account that is receiving assets - @param deliver Asset the dst account will receive - (if issuer of deliver == dst, then accept any issuer) - @param limitQuality Offer crossing BookSteps use this value in an - optimization. If, during direct offer crossing, the - quality of the tip of the book drops below this value, - then evaluating the strand can stop. - @param sendMax Optional asset to send. - @param paths Paths to use to fulfill the payment. Each path in the pathset - contains an ordered collection of the offer books to use and - accounts to ripple through. - @param addDefaultPath Determines if the default path should be included - @param ownerPaysTransferFee false -> charge sender; true -> charge offer - owner - @param offerCrossing false -> payment; true -> offer crossing - @param ammContext counts iterations with AMM offers - @param domainID the domain that order books will use - @param j Journal for logging messages - @return error code and collection of strands -*/ + * Create a Strand for each specified path (including the default path, if + * indicated) + * + * @param sb View for trust lines, balances, and attributes like auth and freeze + * @param src Account that is sending assets + * @param dst Account that is receiving assets + * @param deliver Asset the dst account will receive + * (if issuer of deliver == dst, then accept any issuer) + * @param limitQuality Offer crossing BookSteps use this value in an + * optimization. If, during direct offer crossing, the + * quality of the tip of the book drops below this value, + * then evaluating the strand can stop. + * @param sendMax Optional asset to send. + * @param paths Paths to use to fulfill the payment. Each path in the pathset + * contains an ordered collection of the offer books to use and + * accounts to ripple through. + * @param addDefaultPath Determines if the default path should be included + * @param ownerPaysTransferFee false -> charge sender; true -> charge offer + * owner + * @param offerCrossing false -> payment; true -> offer crossing + * @param ammContext counts iterations with AMM offers + * @param domainID the domain that order books will use + * @param j Journal for logging messages + * @return error code and collection of strands + */ std::pair> toStrands( ReadView const& sb, @@ -441,7 +447,7 @@ toStrands( std::optional const& domainID, beast::Journal j); -/// @cond INTERNAL +/** @cond INTERNAL */ template struct StepImp : public Step { @@ -490,9 +496,9 @@ public: } friend TDerived; }; -/// @endcond +/** @endcond */ -/// @cond INTERNAL +/** @cond INTERNAL */ // Thrown when unexpected errors occur class FlowException : public std::runtime_error { @@ -507,9 +513,9 @@ public: { } }; -/// @endcond +/** @endcond */ -/// @cond INTERNAL +/** @cond INTERNAL */ // Check equal with tolerance bool checkNear(IOUAmount const& expected, IOUAmount const& actual); @@ -523,10 +529,10 @@ checkNear(XRPAmount const& expected, XRPAmount const& actual) { return expected == actual; } -/// @endcond +/** @endcond */ /** - Context needed to build Strand Steps and for error checking + * Context needed to build Strand Steps and for error checking */ struct StrandContext { @@ -541,25 +547,30 @@ struct StrandContext OfferCrossing const offerCrossing; ///< Yes/Sell if offer crossing, not payment bool const isDefaultPath; ///< true if Strand is default path size_t const strandSize; ///< Length of Strand - /** The previous step in the strand. Needed to check the no ripple - constraint + /** + * The previous step in the strand. Needed to check the no ripple + * constraint */ Step const* const prevStep = nullptr; - /** A strand may not include the same account node more than once - in the same currency. In a direct step, an account will show up - at most twice: once as a src and once as a dst (hence the two element - array). The strandSrc and strandDst will only show up once each. - */ + /** + * A strand may not include the same account node more than once + * in the same currency. In a direct step, an account will show up + * at most twice: once as a src and once as a dst (hence the two element + * array). The strandSrc and strandDst will only show up once each. + */ std::array, 2>& seenDirectAssets; - /** A strand may not include an offer that output the same issue more - than once - */ + /** + * A strand may not include an offer that output the same issue more + * than once + */ boost::container::flat_set& seenBookOuts; AMMContext& ammContext; std::optional domainID; // the domain the order book will use beast::Journal const j; - /** StrandContext constructor. */ + /** + * StrandContext constructor. + */ StrandContext( ReadView const& view, std::vector> const& strand, @@ -581,7 +592,7 @@ struct StrandContext beast::Journal j); ///< Journal for logging }; -/// @cond INTERNAL +/** @cond INTERNAL */ namespace test { // Needed for testing bool @@ -659,6 +670,6 @@ isDirectXrpToXrp(Strand const& strand) return false; } } -/// @endcond +/** @endcond */ } // namespace xrpl diff --git a/include/xrpl/tx/paths/detail/StrandFlow.h b/include/xrpl/tx/paths/detail/StrandFlow.h index fe657b2100..c932c49cca 100644 --- a/include/xrpl/tx/paths/detail/StrandFlow.h +++ b/include/xrpl/tx/paths/detail/StrandFlow.h @@ -39,7 +39,9 @@ namespace xrpl { -/** Result of flow() execution of a single Strand. */ +/** + * Result of flow() execution of a single Strand. + */ template struct StrandResult { @@ -56,7 +58,9 @@ struct StrandResult bool inactive = false; ///< Strand should not considered as a further ///< source of liquidity (dry) - /** Strand result constructor */ + /** + * Strand result constructor + */ StrandResult() = default; StrandResult( @@ -83,15 +87,15 @@ struct StrandResult }; /** - Request `out` amount from a strand - - @param baseView Trust lines and balances - @param strand Steps of Accounts to ripple through and offer books to use - @param maxIn Max amount of input allowed - @param out Amount of output requested from the strand - @param j Journal to write log messages to - @return Actual amount in and out from the strand, errors, offers to remove, - and payment sandbox + * Request `out` amount from a strand + * + * @param baseView Trust lines and balances + * @param strand Steps of Accounts to ripple through and offer books to use + * @param maxIn Max amount of input allowed + * @param out Amount of output requested from the strand + * @param j Journal to write log messages to + * @return Actual amount in and out from the strand, errors, offers to remove, + * and payment sandbox */ template StrandResult @@ -296,7 +300,7 @@ flow( } } -/// @cond INTERNAL +/** @cond INTERNAL */ template struct FlowResult { @@ -335,9 +339,9 @@ struct FlowResult { } }; -/// @endcond +/** @endcond */ -/// @cond INTERNAL +/** @cond INTERNAL */ inline std::optional qualityUpperBound(ReadView const& v, Strand const& strand) { @@ -357,10 +361,11 @@ qualityUpperBound(ReadView const& v, Strand const& strand) } return q; }; -/// @endcond +/** @endcond */ -/// @cond INTERNAL -/** Limit remaining out only if one strand and limitQuality is included. +/** @cond INTERNAL */ +/** + * Limit remaining out only if one strand and limitQuality is included. * Targets one path payment with AMM where the average quality is linear * and instant quality is quadratic function of output. Calculating quality * function for the whole strand enables figuring out required output @@ -428,9 +433,9 @@ limitOut( return remainingOut; return std::min(out, remainingOut); }; -/// @endcond +/** @endcond */ -/// @cond INTERNAL +/** @cond INTERNAL */ /* Track the non-dry strands flow will search the non-dry strands (stored in `cur_`) for the best @@ -545,28 +550,28 @@ public: return cur_.size(); } }; -/// @endcond +/** @endcond */ /** - Request `out` amount from a collection of strands - - Attempt to fulfill the payment by using liquidity from the strands in order - from least expensive to most expensive - - @param baseView Trust lines and balances - @param strands Each strand contains the steps of accounts to ripple through - and offer books to use - @param outReq Amount of output requested from the strand - @param partialPayment If true allow less than the full payment - @param offerCrossing If true offer crossing, not handling a standard payment - @param limitQuality If present, the minimum quality for any strand taken - @param sendMaxST If present, the maximum STAmount to send - @param j Journal to write journal messages to - @param ammContext counts iterations with AMM offers - @param flowDebugInfo If pointer is non-null, write flow debug info here - @return Actual amount in and out from the strands, errors, and payment - sandbox -*/ + * Request `out` amount from a collection of strands + * + * Attempt to fulfill the payment by using liquidity from the strands in order + * from least expensive to most expensive + * + * @param baseView Trust lines and balances + * @param strands Each strand contains the steps of accounts to ripple through + * and offer books to use + * @param outReq Amount of output requested from the strand + * @param partialPayment If true allow less than the full payment + * @param offerCrossing If true offer crossing, not handling a standard payment + * @param limitQuality If present, the minimum quality for any strand taken + * @param sendMaxST If present, the maximum STAmount to send + * @param j Journal to write journal messages to + * @param ammContext counts iterations with AMM offers + * @param flowDebugInfo If pointer is non-null, write flow debug info here + * @return Actual amount in and out from the strands, errors, and payment + * sandbox + */ template FlowResult flow( diff --git a/include/xrpl/tx/transactors/account/SignerListSet.h b/include/xrpl/tx/transactors/account/SignerListSet.h index da2274a1de..9f7872a18e 100644 --- a/include/xrpl/tx/transactors/account/SignerListSet.h +++ b/include/xrpl/tx/transactors/account/SignerListSet.h @@ -20,9 +20,9 @@ namespace xrpl { /** -See the README.md for an overview of the SignerListSet transaction that -this class implements. -*/ + * See the README.md for an overview of the SignerListSet transaction that + * this class implements. + */ class SignerListSet : public Transactor { private: diff --git a/include/xrpl/tx/transactors/dex/AMMBid.h b/include/xrpl/tx/transactors/dex/AMMBid.h index fa257696e8..9d8eb8578b 100644 --- a/include/xrpl/tx/transactors/dex/AMMBid.h +++ b/include/xrpl/tx/transactors/dex/AMMBid.h @@ -11,7 +11,8 @@ namespace xrpl { -/** AMMBid implements AMM bid Transactor. +/** + * AMMBid implements AMM bid Transactor. * This is a mechanism for an AMM instance to auction-off * the trading advantages to users (arbitrageurs) at a discounted * TradingFee for a 24 hour slot. Any account that owns corresponding diff --git a/include/xrpl/tx/transactors/dex/AMMClawback.h b/include/xrpl/tx/transactors/dex/AMMClawback.h index 54eb3c9f27..3f3f2c421a 100644 --- a/include/xrpl/tx/transactors/dex/AMMClawback.h +++ b/include/xrpl/tx/transactors/dex/AMMClawback.h @@ -56,7 +56,8 @@ private: TER applyGuts(Sandbox& view); - /** Withdraw both assets by providing maximum amount of asset1, + /** + * Withdraw both assets by providing maximum amount of asset1, * asset2's amount will be calculated according to the current proportion. * Since it is two-asset withdrawal, tfee is omitted. * @param view diff --git a/include/xrpl/tx/transactors/dex/AMMContext.h b/include/xrpl/tx/transactors/dex/AMMContext.h index 65954044be..b878e4f10b 100644 --- a/include/xrpl/tx/transactors/dex/AMMContext.h +++ b/include/xrpl/tx/transactors/dex/AMMContext.h @@ -6,7 +6,8 @@ namespace xrpl { -/** Maintains AMM info per overall payment engine execution and +/** + * Maintains AMM info per overall payment engine execution and * individual iteration. * Only one instance of this class is created in Flow.cpp::flow(). * The reference is percolated through calls to AMMLiquidity class, @@ -84,7 +85,8 @@ public: return accountID_; } - /** Strand execution may fail. Reset the flag at the start + /** + * Strand execution may fail. Reset the flag at the start * of each payment engine iteration. */ void diff --git a/include/xrpl/tx/transactors/dex/AMMCreate.h b/include/xrpl/tx/transactors/dex/AMMCreate.h index 188af8d4ef..5260cf31cd 100644 --- a/include/xrpl/tx/transactors/dex/AMMCreate.h +++ b/include/xrpl/tx/transactors/dex/AMMCreate.h @@ -11,34 +11,35 @@ namespace xrpl { -/** AMMCreate implements Automatic Market Maker(AMM) creation Transactor. - * It creates a new AMM instance with two tokens. Any trader, or Liquidity - * Provider (LP), can create the AMM instance and receive in return shares - * of the AMM pool in the form of LPTokens. The number of tokens that LP gets - * are determined by LPTokens = sqrt(A * B), where A and B is the current - * composition of the AMM pool. LP can add (AMMDeposit) or withdraw - * (AMMWithdraw) tokens from AMM and - * AMM can be used transparently in the payment or offer crossing transactions. - * Trading fee is charged to the traders for the trades executed against - * AMM instance. The fee is added to the AMM pool and distributed to the LPs - * in proportion to the LPTokens upon liquidity removal. The fee can be voted - * on by LP's (AMMVote). LP's can continuously bid (AMMBid) for the 24 hour - * auction slot, which enables LP's to trade at zero trading fee. - * AMM instance creates AccountRoot object with disabled master key - * for book-keeping of XRP balance if one of the tokens - * is XRP, a trustline for each IOU token, a trustline to keep track - * of LPTokens, and ltAMM ledger object. AccountRoot ID is generated - * internally from the parent's hash. ltAMM's object ID is +/** + * AMMCreate implements Automatic Market Maker(AMM) creation Transactor. + * It creates a new AMM instance with two tokens. Any trader, or Liquidity + * Provider (LP), can create the AMM instance and receive in return shares + * of the AMM pool in the form of LPTokens. The number of tokens that LP gets + * are determined by LPTokens = sqrt(A * B), where A and B is the current + * composition of the AMM pool. LP can add (AMMDeposit) or withdraw + * (AMMWithdraw) tokens from AMM and + * AMM can be used transparently in the payment or offer crossing transactions. + * Trading fee is charged to the traders for the trades executed against + * AMM instance. The fee is added to the AMM pool and distributed to the LPs + * in proportion to the LPTokens upon liquidity removal. The fee can be voted + * on by LP's (AMMVote). LP's can continuously bid (AMMBid) for the 24 hour + * auction slot, which enables LP's to trade at zero trading fee. + * AMM instance creates AccountRoot object with disabled master key + * for book-keeping of XRP balance if one of the tokens + * is XRP, a trustline for each IOU token, a trustline to keep track + * of LPTokens, and ltAMM ledger object. AccountRoot ID is generated + * internally from the parent's hash. ltAMM's object ID is * hash{token1.currency, token1.issuer, token2.currency, token2.issuer}, where * issue1 < issue2. ltAMM object provides mapping from the hash to AccountRoot * ID and contains: AMMAccount - AMM AccountRoot ID. TradingFee - AMM voted * TradingFee. VoteSlots - Array of VoteEntry, contains fee vote information. - * AuctionSlot - Auction slot, contains discounted fee bid information. - * LPTokenBalance - LPTokens outstanding balance. - * AMMToken - currency/issuer information for AMM tokens. - * AMMDeposit, AMMWithdraw, AMMVote, and AMMBid transactions use the hash - * to access AMM instance. - * @see [XLS30d:Creating AMM instance on + * AuctionSlot - Auction slot, contains discounted fee bid information. + * LPTokenBalance - LPTokens outstanding balance. + * AMMToken - currency/issuer information for AMM tokens. + * AMMDeposit, AMMWithdraw, AMMVote, and AMMBid transactions use the hash + * to access AMM instance. + * @see [XLS30d:Creating AMM instance on * XRPL](https://github.com/XRPLF/XRPL-Standards/discussions/78) */ class AMMCreate : public Transactor @@ -62,7 +63,9 @@ public: static TER preclaim(PreclaimContext const& ctx); - /** Attempt to create the AMM instance. */ + /** + * Attempt to create the AMM instance. + */ TER doApply() override; diff --git a/include/xrpl/tx/transactors/dex/AMMDelete.h b/include/xrpl/tx/transactors/dex/AMMDelete.h index 4a0905fe10..abf800c4d1 100644 --- a/include/xrpl/tx/transactors/dex/AMMDelete.h +++ b/include/xrpl/tx/transactors/dex/AMMDelete.h @@ -11,7 +11,8 @@ namespace xrpl { -/** AMMDelete implements AMM delete transactor. This is a mechanism to +/** + * AMMDelete implements AMM delete transactor. This is a mechanism to * delete AMM in an empty state when the number of LP tokens is 0. * AMMDelete deletes the trustlines up to configured maximum. If all * trustlines are deleted then AMM ltAMM and root account are deleted. diff --git a/include/xrpl/tx/transactors/dex/AMMDeposit.h b/include/xrpl/tx/transactors/dex/AMMDeposit.h index b87db19b2d..2959f1b10a 100644 --- a/include/xrpl/tx/transactors/dex/AMMDeposit.h +++ b/include/xrpl/tx/transactors/dex/AMMDeposit.h @@ -20,7 +20,8 @@ namespace xrpl { class Sandbox; -/** AMMDeposit implements AMM deposit Transactor. +/** + * AMMDeposit implements AMM deposit Transactor. * The deposit transaction is used to add liquidity to the AMM instance pool, * thus obtaining some share of the instance's pools in the form of LPTokens. * If the trader deposits proportional values of both assets without changing @@ -92,7 +93,8 @@ private: std::pair applyGuts(Sandbox& view); - /** Deposit requested assets and token amount into LP account. + /** + * Deposit requested assets and token amount into LP account. * Return new total LPToken balance. * @param view * @param ammAccount @@ -121,7 +123,8 @@ private: std::optional const& lpTokensDepositMin, std::uint16_t tfee); - /** Equal asset deposit (LPTokens) for the specified share of + /** + * Equal asset deposit (LPTokens) for the specified share of * the AMM instance pools. The trading fee is not charged. * @param view * @param ammAccount @@ -146,7 +149,8 @@ private: std::optional const& deposit2Min, std::uint16_t tfee); - /** Equal asset deposit (Asset1In, Asset2In) with the constraint on + /** + * Equal asset deposit (Asset1In, Asset2In) with the constraint on * the maximum amount of both assets that the trader is willing to deposit. * The trading fee is not charged. * @param view @@ -172,7 +176,8 @@ private: std::optional const& lpTokensDepositMin, std::uint16_t tfee); - /** Single asset deposit (Asset1In) by the amount. + /** + * Single asset deposit (Asset1In) by the amount. * The trading fee is charged. * @param view * @param ammAccount @@ -193,7 +198,8 @@ private: std::optional const& lpTokensDepositMin, std::uint16_t tfee); - /** Single asset deposit (Asset1In, LPTokens) by the tokens. + /** + * Single asset deposit (Asset1In, LPTokens) by the tokens. * The trading fee is charged. * @param view * @param ammAccount @@ -214,7 +220,8 @@ private: STAmount const& lpTokensDeposit, std::uint16_t tfee); - /** Single asset deposit (Asset1In, EPrice) with two constraints. + /** + * Single asset deposit (Asset1In, EPrice) with two constraints. * The trading fee is charged. * @param view * @param ammAccount @@ -235,7 +242,8 @@ private: STAmount const& ePrice, std::uint16_t tfee); - /** Equal deposit in empty AMM state (LP tokens balance is 0) + /** + * Equal deposit in empty AMM state (LP tokens balance is 0) * @param view * @param ammAccount * @param amount requested asset1 deposit amount diff --git a/include/xrpl/tx/transactors/dex/AMMVote.h b/include/xrpl/tx/transactors/dex/AMMVote.h index 10ad284bb3..4d63a98534 100644 --- a/include/xrpl/tx/transactors/dex/AMMVote.h +++ b/include/xrpl/tx/transactors/dex/AMMVote.h @@ -11,7 +11,8 @@ namespace xrpl { -/** AMMVote implements AMM vote Transactor. +/** + * AMMVote implements AMM vote Transactor. * This transactor allows for the TradingFee of the AMM instance be a votable * parameter. Any account (LP) that holds the corresponding LPTokens can cast * a vote using the new AMMVote transaction. VoteSlots array in ltAMM object diff --git a/include/xrpl/tx/transactors/dex/AMMWithdraw.h b/include/xrpl/tx/transactors/dex/AMMWithdraw.h index 8f6700037d..7004dd57c1 100644 --- a/include/xrpl/tx/transactors/dex/AMMWithdraw.h +++ b/include/xrpl/tx/transactors/dex/AMMWithdraw.h @@ -22,7 +22,8 @@ namespace xrpl { class Sandbox; -/** AMMWithdraw implements AMM withdraw Transactor. +/** + * AMMWithdraw implements AMM withdraw Transactor. * The withdraw transaction is used to remove liquidity from the AMM instance * pool, thus redeeming some share of the pools that one owns in the form * of LPTokens. If the trader withdraws proportional values of both assets @@ -96,7 +97,8 @@ public: ReadView const& view, beast::Journal const& j) override; - /** Equal-asset withdrawal (LPTokens) of some AMM instance pools + /** + * Equal-asset withdrawal (LPTokens) of some AMM instance pools * shares represented by the number of LPTokens . * The trading fee is not charged. * @param view @@ -129,7 +131,8 @@ public: XRPAmount const& priorBalance, beast::Journal const& journal); - /** Withdraw requested assets and token from AMM into LP account. + /** + * Withdraw requested assets and token from AMM into LP account. * Return new total LPToken balance and the withdrawn amounts for both * assets. * @param view @@ -173,15 +176,18 @@ public: beast::Journal const& journal); private: - /** Returns IgnoreFreeze when the withdrawer is the issuer of a pool - * asset (post-fixCleanup3_3_0), ZeroIfFrozen otherwise. */ + /** + * Returns IgnoreFreeze when the withdrawer is the issuer of a pool + * asset (post-fixCleanup3_3_0), ZeroIfFrozen otherwise. + */ [[nodiscard]] FreezeHandling issuerFreezeHandling() const; std::pair applyGuts(Sandbox& view); - /** Withdraw requested assets and token from AMM into LP account. + /** + * Withdraw requested assets and token from AMM into LP account. * Return new total LPToken balance. * @param view * @param ammSle AMM ledger entry @@ -205,7 +211,8 @@ private: STAmount const& lpTokensWithdraw, std::uint16_t tfee); - /** Equal-asset withdrawal (LPTokens) of some AMM instance pools + /** + * Equal-asset withdrawal (LPTokens) of some AMM instance pools * shares represented by the number of LPTokens . * The trading fee is not charged. * @param view @@ -230,7 +237,8 @@ private: STAmount const& lpTokensWithdraw, std::uint16_t tfee); - /** Withdraw both assets (Asset1Out, Asset2Out) with the constraints + /** + * Withdraw both assets (Asset1Out, Asset2Out) with the constraints * on the maximum amount of each asset that the trader is willing * to withdraw. The trading fee is not charged. * @param view @@ -255,7 +263,8 @@ private: STAmount const& amount2, std::uint16_t tfee); - /** Single asset withdrawal (Asset1Out) equivalent to the amount specified + /** + * Single asset withdrawal (Asset1Out) equivalent to the amount specified * in Asset1Out. The trading fee is charged. * @param view * @param ammAccount @@ -275,7 +284,8 @@ private: STAmount const& amount, std::uint16_t tfee); - /** Single asset withdrawal (Asset1Out, LPTokens) proportional + /** + * Single asset withdrawal (Asset1Out, LPTokens) proportional * to the share specified by tokens. The trading fee is charged. * @param view * @param ammAccount @@ -297,7 +307,8 @@ private: STAmount const& lpTokensWithdraw, std::uint16_t tfee); - /** Withdraw single asset (Asset1Out, EPrice) with two constraints. + /** + * Withdraw single asset (Asset1Out, EPrice) with two constraints. * The trading fee is charged. * @param view * @param ammAccount @@ -319,7 +330,9 @@ private: STAmount const& ePrice, std::uint16_t tfee); - /** Check from the flags if it's withdraw all */ + /** + * Check from the flags if it's withdraw all + */ static WithdrawAll isWithdrawAll(STTx const& tx); }; diff --git a/include/xrpl/tx/transactors/dex/OfferCreate.h b/include/xrpl/tx/transactors/dex/OfferCreate.h index a3bf7626f9..0ce34646dd 100644 --- a/include/xrpl/tx/transactors/dex/OfferCreate.h +++ b/include/xrpl/tx/transactors/dex/OfferCreate.h @@ -28,13 +28,17 @@ namespace xrpl { class PaymentSandbox; class Sandbox; -/** Transactor specialized for creating offers in the ledger. */ +/** + * Transactor specialized for creating offers in the ledger. + */ class OfferCreate : public Transactor { public: static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Custom; - /** Construct a Transactor subclass that creates an offer in the ledger. */ + /** + * Construct a Transactor subclass that creates an offer in the ledger. + */ explicit OfferCreate(ApplyContext& ctx) : Transactor(ctx) { } @@ -48,15 +52,21 @@ public: static std::uint32_t getFlagsMask(PreflightContext const& ctx); - /** Enforce constraints beyond those of the Transactor base class. */ + /** + * Enforce constraints beyond those of the Transactor base class. + */ static NotTEC preflight(PreflightContext const& ctx); - /** Enforce constraints beyond those of the Transactor base class. */ + /** + * Enforce constraints beyond those of the Transactor base class. + */ static TER preclaim(PreclaimContext const& ctx); - /** Precondition: fee collection is likely. Attempt to create the offer. */ + /** + * Precondition: fee collection is likely. Attempt to create the offer. + */ TER doApply() override; diff --git a/include/xrpl/tx/transactors/lending/LoanManage.h b/include/xrpl/tx/transactors/lending/LoanManage.h index c8a5584131..e641e03dd1 100644 --- a/include/xrpl/tx/transactors/lending/LoanManage.h +++ b/include/xrpl/tx/transactors/lending/LoanManage.h @@ -36,7 +36,8 @@ public: static TER preclaim(PreclaimContext const& ctx); - /** Helper function that might be needed by other transactors + /** + * Helper function that might be needed by other transactors */ static TER defaultLoan( @@ -47,7 +48,8 @@ public: Asset const& vaultAsset, beast::Journal j); - /** Helper function that might be needed by other transactors + /** + * Helper function that might be needed by other transactors */ static TER impairLoan( @@ -57,7 +59,8 @@ public: Asset const& vaultAsset, beast::Journal j); - /** Helper function that might be needed by other transactors + /** + * Helper function that might be needed by other transactors */ [[nodiscard]] static TER unimpairLoan( diff --git a/include/xrpl/tx/transactors/oracle/OracleDelete.h b/include/xrpl/tx/transactors/oracle/OracleDelete.h index f9e9230527..d44065d7bb 100644 --- a/include/xrpl/tx/transactors/oracle/OracleDelete.h +++ b/include/xrpl/tx/transactors/oracle/OracleDelete.h @@ -14,13 +14,13 @@ namespace xrpl { /** - Price Oracle is a system that acts as a bridge between - a blockchain network and the external world, providing off-chain price data - to decentralized applications (dApps) on the blockchain. This implementation - conforms to the requirements specified in the XLS-47d. - - The OracleDelete transactor implements the deletion of Oracle objects. -*/ + * Price Oracle is a system that acts as a bridge between + * a blockchain network and the external world, providing off-chain price data + * to decentralized applications (dApps) on the blockchain. This implementation + * conforms to the requirements specified in the XLS-47d. + * + * The OracleDelete transactor implements the deletion of Oracle objects. + */ class OracleDelete : public Transactor { diff --git a/include/xrpl/tx/transactors/oracle/OracleSet.h b/include/xrpl/tx/transactors/oracle/OracleSet.h index e95970923b..b6aed3f4fc 100644 --- a/include/xrpl/tx/transactors/oracle/OracleSet.h +++ b/include/xrpl/tx/transactors/oracle/OracleSet.h @@ -12,13 +12,13 @@ namespace xrpl { /** - Price Oracle is a system that acts as a bridge between - a blockchain network and the external world, providing off-chain price data - to decentralized applications (dApps) on the blockchain. This implementation - conforms to the requirements specified in the XLS-47d. - - The OracleSet transactor implements creating or updating Oracle objects. -*/ + * Price Oracle is a system that acts as a bridge between + * a blockchain network and the external world, providing off-chain price data + * to decentralized applications (dApps) on the blockchain. This implementation + * conforms to the requirements specified in the XLS-47d. + * + * The OracleSet transactor implements creating or updating Oracle objects. + */ class OracleSet : public Transactor { diff --git a/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.h b/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.h index 5a07262a3b..6bd68a0142 100644 --- a/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.h +++ b/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.h @@ -26,7 +26,9 @@ public: static TER preclaim(PreclaimContext const& ctx); - /** Attempt to delete the Permissioned Domain. */ + /** + * Attempt to delete the Permissioned Domain. + */ TER doApply() override; diff --git a/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.h b/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.h index 38de800284..cb5d341c50 100644 --- a/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.h +++ b/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.h @@ -29,7 +29,9 @@ public: static TER preclaim(PreclaimContext const& ctx); - /** Attempt to create the Permissioned Domain. */ + /** + * Attempt to create the Permissioned Domain. + */ TER doApply() override; diff --git a/include/xrpl/tx/transactors/sponsor/SponsorshipSet.h b/include/xrpl/tx/transactors/sponsor/SponsorshipSet.h new file mode 100644 index 0000000000..3310c995ae --- /dev/null +++ b/include/xrpl/tx/transactors/sponsor/SponsorshipSet.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace xrpl { + +class SponsorshipSet : public Transactor +{ +public: + static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal; + + explicit SponsorshipSet(ApplyContext& ctx) : Transactor(ctx) + { + } + + static TxConsequences + makeTxConsequences(PreflightContext const& ctx); + + static std::uint32_t + getFlagsMask(PreflightContext const& ctx); + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; + + void + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; +}; + +} // namespace xrpl diff --git a/include/xrpl/tx/transactors/sponsor/SponsorshipTransfer.h b/include/xrpl/tx/transactors/sponsor/SponsorshipTransfer.h new file mode 100644 index 0000000000..cf0022ab72 --- /dev/null +++ b/include/xrpl/tx/transactors/sponsor/SponsorshipTransfer.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace xrpl { + +class SponsorshipTransfer : public Transactor +{ +public: + static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal; + + explicit SponsorshipTransfer(ApplyContext& ctx) : Transactor(ctx) + { + } + + static std::uint32_t + getFlagsMask(PreflightContext const& ctx); + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; + + void + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; +}; + +} // namespace xrpl diff --git a/include/xrpl/tx/transactors/system/TicketCreate.h b/include/xrpl/tx/transactors/system/TicketCreate.h index 2a1036c732..f249fd7db2 100644 --- a/include/xrpl/tx/transactors/system/TicketCreate.h +++ b/include/xrpl/tx/transactors/system/TicketCreate.h @@ -57,15 +57,21 @@ public: static TxConsequences makeTxConsequences(PreflightContext const& ctx); - /** Enforce constraints beyond those of the Transactor base class. */ + /** + * Enforce constraints beyond those of the Transactor base class. + */ static NotTEC preflight(PreflightContext const& ctx); - /** Enforce constraints beyond those of the Transactor base class. */ + /** + * Enforce constraints beyond those of the Transactor base class. + */ static TER preclaim(PreclaimContext const& ctx); - /** Precondition: fee collection is likely. Attempt to create ticket(s). */ + /** + * Precondition: fee collection is likely. Attempt to create ticket(s). + */ TER doApply() override; diff --git a/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h b/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h index 045911c7ae..5d35f65f44 100644 --- a/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h +++ b/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h @@ -76,7 +76,7 @@ public: beast::Journal const& j) override; static std::expected - create(ApplyView& view, beast::Journal journal, MPTCreateArgs const& args); + create(ApplyViewContext ctx, beast::Journal journal, MPTCreateArgs const& args); }; } // namespace xrpl diff --git a/nix/ci-env.nix b/nix/ci-env.nix index f823f71de0..9b754af97d 100644 --- a/nix/ci-env.nix +++ b/nix/ci-env.nix @@ -4,14 +4,16 @@ ... }: let - inherit (import ./packages.nix { inherit pkgs; }) commonPackages; - inherit (pkgs) lib; + inherit (import ./packages.nix { inherit pkgs; }) + commonPackages + gccPackage + llvmPackages + llvmVersion + ; - # Underlying compiler toolchains to wrap. Bump these in one place to - # roll the whole environment forward. - customGccPackage = pkgs.gcc15; - customLlvmPackages = pkgs.llvmPackages_22; - customClangMajor = lib.versions.major (lib.getVersion customLlvmPackages.clang-unwrapped); + # Underlying compiler toolchains to wrap (versions pinned in packages.nix). + customGccPackage = gccPackage; + customLlvmPackages = llvmPackages; # binutils wrapped to emit binaries that reference the custom glibc # (dynamic linker path, library search path, RPATH). @@ -90,7 +92,7 @@ let extraBuildCommands = '' rsrc="$out/resource-root" mkdir "$rsrc" - ln -s "${customLlvmPackages.clang-unwrapped.lib}/lib/clang/${customClangMajor}/include" "$rsrc/include" + ln -s "${customLlvmPackages.clang-unwrapped.lib}/lib/clang/${toString llvmVersion}/include" "$rsrc/include" ln -s "${customCompilerRt.out}/lib" "$rsrc/lib" ln -s "${customCompilerRt.out}/share" "$rsrc/share" || true echo "-resource-dir=$rsrc" >> $out/nix-support/cc-cflags diff --git a/nix/devshell.nix b/nix/devshell.nix index 1bd7ea4c0c..34f173ef08 100644 --- a/nix/devshell.nix +++ b/nix/devshell.nix @@ -1,127 +1,57 @@ { pkgs, ... }: let - inherit (import ./packages.nix { inherit pkgs; }) commonPackages; + inherit (import ./packages.nix { inherit pkgs; }) + commonPackages + gccVersion + llvmPackages + ; - # Supported compiler versions - gccVersion = pkgs.lib.range 13 15; - clangVersions = pkgs.lib.range 18 21; + # Plain nixpkgs stdenvs — no custom glibc, unlike ci-env.nix. + gccStdenv = pkgs."gcc${toString gccVersion}Stdenv"; + clangStdenv = llvmPackages.stdenv; - defaultCompiler = if pkgs.stdenv.isDarwin then "apple-clang" else "gcc"; - defaultGccVersion = pkgs.lib.last gccVersion; - defaultClangVersion = pkgs.lib.last clangVersions; - - strToCompilerEnv = - compiler: version: - ( - if compiler == "gcc" then - let - gccPkg = pkgs."gcc${toString version}Stdenv" or null; - in - if gccPkg != null && builtins.elem version gccVersion then - gccPkg - else - throw "Invalid GCC version: ${toString version}. Must be one of: ${toString gccVersion}" - else if compiler == "clang" then - let - clangPkg = pkgs."llvmPackages_${toString version}".stdenv or null; - in - if clangPkg != null && builtins.elem version clangVersions then - clangPkg - else - throw "Invalid Clang version: ${toString version}. Must be one of: ${toString clangVersions}" - else if compiler == "apple-clang" || compiler == "none" then - pkgs.stdenvNoCC - else - throw "Invalid compiler: ${compiler}. Must be one of: gcc, clang, apple-clang, none" - ); - - # Helper function to create a shell with a specific compiler + # compilerName is the command used to print the version, or null for none. makeShell = { - compiler ? defaultCompiler, - version ? ( - if compiler == "gcc" then - defaultGccVersion - else if compiler == "clang" then - defaultClangVersion - else - null - ), + stdenv, + compilerName, }: let - compilerStdEnv = strToCompilerEnv compiler version; - - compilerName = - if compiler == "apple-clang" then - "clang" - else if compiler == "none" then - null - else - compiler; - - gccOnMacWarning = - if pkgs.stdenv.isDarwin && compiler == "gcc" then - '' - echo "WARNING: Using GCC on macOS with Conan may not work." - echo " Consider using 'nix develop .#clang' or the default shell instead." - echo "" - '' - else - ""; - compilerVersion = - if compilerName != null then + if compilerName == null then + ''echo "No compiler specified - using system compiler"'' + else '' echo "Compiler: " ${compilerName} --version - '' - else - '' - echo "No compiler specified - using system compiler" ''; - - shellAttrs = { - packages = commonPackages; - - shellHook = '' - echo "Welcome to xrpld development shell"; - ${gccOnMacWarning}${compilerVersion} - ''; - }; in - pkgs.mkShell.override { stdenv = compilerStdEnv; } shellAttrs; - - # Generate shells for each compiler version - gccShells = builtins.listToAttrs ( - map (version: { - name = "gcc${toString version}"; - value = makeShell { - compiler = "gcc"; - version = version; - }; - }) gccVersion - ); - - clangShells = builtins.listToAttrs ( - map (version: { - name = "clang${toString version}"; - value = makeShell { - compiler = "clang"; - version = version; - }; - }) clangVersions - ); - + (pkgs.mkShell.override { inherit stdenv; }) { + packages = commonPackages; + shellHook = '' + echo "Welcome to xrpld development shell"; + ${compilerVersion} + ''; + }; in -gccShells -// clangShells -// { - # Default shells - default = makeShell { }; - gcc = makeShell { compiler = "gcc"; }; - clang = makeShell { compiler = "clang"; }; +rec { + # macOS: Nix Clang. Linux: Nix GCC. + default = if pkgs.stdenv.isDarwin then clang else gcc; - # No compiler - no-compiler = makeShell { compiler = "none"; }; - apple-clang = makeShell { compiler = "apple-clang"; }; + gcc = makeShell { + stdenv = gccStdenv; + compilerName = "gcc"; + }; + + clang = makeShell { + stdenv = clangStdenv; + compilerName = "clang"; + }; + + # Nix provides no compiler; use the one from your system (e.g. Apple Clang). + no-compiler = makeShell { + stdenv = pkgs.stdenvNoCC; + compilerName = null; + }; + apple-clang = no-compiler; } diff --git a/nix/packages.nix b/nix/packages.nix index bcadfe7456..bf10ee3712 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -1,15 +1,33 @@ { pkgs }: let + # Compiler versions used across the dev shell and the CI environment. + gccVersion = 15; + llvmVersion = 22; + + gccPackage = pkgs."gcc${toString gccVersion}"; + llvmPackages = pkgs."llvmPackages_${toString llvmVersion}"; + + # Bound explicitly so it tracks llvmPackages above, not the `with pkgs` default. + clangTools = llvmPackages.clang-tools; + # In LLVM 22, run-clang-tidy.py moved from share/clang/ to bin/, so nixpkgs # clang-tools no longer links it. Wrap it manually. runClangTidy = pkgs.writeShellScriptBin "run-clang-tidy" '' - exec ${pkgs.python3}/bin/python3 ${pkgs.llvmPackages_22.clang-unwrapped}/bin/run-clang-tidy "$@" + exec ${pkgs.python3}/bin/python3 ${llvmPackages.clang-unwrapped}/bin/run-clang-tidy "$@" ''; in { + inherit + gccVersion + llvmVersion + gccPackage + llvmPackages + ; + commonPackages = with pkgs; [ ccache clangbuildanalyzer + clangTools cmake conan curlMinimal # needed for codecov/codecov-action @@ -23,7 +41,6 @@ in gnumake gnupg # needed for signing commits & codecov/codecov-action graphviz - llvmPackages_22.clang-tools less # needed for git diff mold nettools # provides netstat, used to debug failures in CI diff --git a/src/libxrpl/basics/Number.cpp b/src/libxrpl/basics/Number.cpp index 4d7a821040..d08fd23016 100644 --- a/src/libxrpl/basics/Number.cpp +++ b/src/libxrpl/basics/Number.cpp @@ -14,7 +14,6 @@ #include #include #include -#include #include #ifdef _MSC_VER @@ -31,74 +30,103 @@ namespace xrpl { thread_local Number::RoundingMode Number::mode = Number::RoundingMode::ToNearest; thread_local std::reference_wrapper Number::kRange = - MantissaRange::getMantissaRange(MantissaRange::MantissaScale::Large); + MantissaRange::Access::mantissaRange(MantissaRange::MantissaScale::Large330); -std::set const& -MantissaRange::getAllScales() +std::string +to_string(MantissaRange::MantissaScale const& scale) { - static std::set const kScales = { - MantissaRange::MantissaScale::Small, - MantissaRange::MantissaScale::LargeLegacy, - MantissaRange::MantissaScale::Large, - }; - return kScales; + switch (scale) + { + case MantissaRange::MantissaScale::Small: + return "Small"; + case MantissaRange::MantissaScale::LargeLegacy: + return "LargeLegacy"; + case MantissaRange::MantissaScale::Large320: + return "Large320"; + case MantissaRange::MantissaScale::Large330: + return "Large330"; + default: + throw std::runtime_error("Bad scale"); // LCOV_EXCL_LINE + } } -std::unordered_map const& -MantissaRange::getRanges() +std::string +to_string(Number::RoundingMode const& round) { - static auto const kMap = []() { - std::unordered_map map; - for (auto const scale : getAllScales()) - { - map.emplace(scale, scale); - } - - // Use these constexpr declarations to do static_asserts to verify the MantissaRanges are - // created correctly, but nothing else. - { - [[maybe_unused]] - constexpr static MantissaRange kRange{MantissaRange::MantissaScale::Small}; - static_assert(isPowerOfTen(kRange.min)); - static_assert(kRange.min == 1'000'000'000'000'000LL); - static_assert(kRange.max == 9'999'999'999'999'999LL); - static_assert(kRange.log == 15); - static_assert(kRange.min < Number::kMaxRep); - static_assert(kRange.max < Number::kMaxRep); - static_assert(kRange.cuspRoundingFixEnabled == CuspRoundingFix::Disabled); - } - { - [[maybe_unused]] - constexpr static MantissaRange kRange{MantissaRange::MantissaScale::LargeLegacy}; - static_assert(isPowerOfTen(kRange.min)); - static_assert(kRange.min == 1'000'000'000'000'000'000ULL); - static_assert(kRange.max == rep(9'999'999'999'999'999'999ULL)); - static_assert(kRange.log == 18); - static_assert(kRange.min < Number::kMaxRep); - static_assert(kRange.max > Number::kMaxRep); - static_assert(kRange.cuspRoundingFixEnabled == CuspRoundingFix::Disabled); - } - { - [[maybe_unused]] - constexpr static MantissaRange kRange{MantissaRange::MantissaScale::Large}; - static_assert(isPowerOfTen(kRange.min)); - static_assert(kRange.min == 1'000'000'000'000'000'000ULL); - static_assert(kRange.max == rep(9'999'999'999'999'999'999ULL)); - static_assert(kRange.log == 18); - static_assert(kRange.min < Number::kMaxRep); - static_assert(kRange.max > Number::kMaxRep); - static_assert(kRange.cuspRoundingFixEnabled == CuspRoundingFix::Enabled); - } - return map; - }(); - - return kMap; + switch (round) + { + case Number::RoundingMode::ToNearest: + return "ToNearest"; + case Number::RoundingMode::TowardsZero: + return "TowardsZero"; + case Number::RoundingMode::Downward: + return "Downward"; + case Number::RoundingMode::Upward: + return "Upward"; + default: + throw std::runtime_error("Bad rounding mode"); // LCOV_EXCL_LINE + } } -MantissaRange const& -MantissaRange::getMantissaRange(MantissaScale scale) +constexpr MantissaRange const& +MantissaRange::Access::mantissaRange(MantissaScale scale) { - return getRanges().at(scale); + static constexpr MantissaRange kSmall{MantissaScale::Small}; + static constexpr MantissaRange kLegacy{MantissaScale::LargeLegacy}; + static constexpr MantissaRange kLarge320{MantissaScale::Large320}; + static constexpr MantissaRange kLarge330{MantissaScale::Large330}; + + switch (scale) + { + case MantissaScale::Small: + return kSmall; + case MantissaScale::LargeLegacy: + return kLegacy; + case MantissaScale::Large320: + return kLarge320; + case MantissaScale::Large330: + return kLarge330; + } + throw std::logic_error("Unknown mantissa scale"); + + // static_asserts are checked at compile time, so it doesn't matter where in the function they + // are located. For readability of the main body, put them after it. + + // Small + static_assert(isPowerOfTen(kSmall.min)); + static_assert(kSmall.min == 1'000'000'000'000'000LL); + static_assert(kSmall.max == 9'999'999'999'999'999LL); + static_assert(kSmall.log == 15); + static_assert(kSmall.min < Number::kMaxRep); + static_assert(kSmall.max < Number::kMaxRep); + static_assert(kSmall.cuspRoundingFix == CuspRoundingFix::Disabled); + + // LargeLegacy + static_assert(isPowerOfTen(kLegacy.min)); + static_assert(kLegacy.min == 1'000'000'000'000'000'000ULL); + static_assert(kLegacy.max == rep(9'999'999'999'999'999'999ULL)); + static_assert(kLegacy.log == 18); + static_assert(kLegacy.min < Number::kMaxRep); + static_assert(kLegacy.max > Number::kMaxRep); + static_assert(kLegacy.cuspRoundingFix == CuspRoundingFix::Disabled); + + // Large320 + static_assert(isPowerOfTen(kLarge320.min)); + static_assert(kLarge320.min == 1'000'000'000'000'000'000ULL); + static_assert(kLarge320.max == rep(9'999'999'999'999'999'999ULL)); + static_assert(kLarge320.log == 18); + static_assert(kLarge320.min < Number::kMaxRep); + static_assert(kLarge320.max > Number::kMaxRep); + static_assert(kLarge320.cuspRoundingFix == CuspRoundingFix::Enabled320); + + // Large330 + static_assert(isPowerOfTen(kLarge330.min)); + static_assert(kLarge330.min == 1'000'000'000'000'000'000ULL); + static_assert(kLarge330.max == rep(9'999'999'999'999'999'999ULL)); + static_assert(kLarge330.log == 18); + static_assert(kLarge330.min < Number::kMaxRep); + static_assert(kLarge330.max > Number::kMaxRep); + static_assert(kLarge330.cuspRoundingFix == CuspRoundingFix::Enabled330); } Number::RoundingMode @@ -124,7 +152,7 @@ Number::setMantissaScale(MantissaRange::MantissaScale scale) { if (!MantissaRange::getAllScales().contains(scale)) logicError("Unknown mantissa scale"); - kRange = MantissaRange::getMantissaRange(scale); + kRange = MantissaRange::Access::mantissaRange(scale); } // Optimization equivalent to: @@ -155,15 +183,39 @@ divu10(uint128_t& u) return r; } -// Guard - -// The Guard class is used to temporarily add extra digits of -// precision to an operation. This enables the final result -// to be correctly rounded to the internal precision of Number. - template concept UnsignedMantissa = std::is_unsigned_v || std::is_same_v; +/** + * Guard + * + * The Guard class is used to temporarily add extra digits of + * precision to an operation. This enables the final result + * to be correctly rounded to the internal precision of Number. + * + * At its core, the Guard really only needs three pieces of information to determine how to round: + * 1. The rounding mode + * 2. The last digit dropped from the mantissa (i.e. the first digit after the decimal point). + * (first byte of digits_) + * 3. Whether any other non-zero digits were dropped from the mantissa. (remaining bytes of digits_ + * and xbit_) + * + * Upward and Downward rounding modes round the unsigned mantissa toward or away from zero + * depending on whether the sign is negative (sbit_). For positive values, Upward is away, and + * Downward is toward. For negative values, that's reversed. For simplicity, I'm going to describe + * the logic using "TowardZero" and "AwayFromZero". + * + * TowardZero is the easiest rounding mode. It always rounds down. digits_ and xbit_ are + * irrelevant. + * AwayFromZero is almost as simple. If both "digits_" and "xbit_" are zero (0), it rounds down. + * Else it rounds up. + * ToNearest is only a little more complicated. If the last dropped digit is < 5, then round + * down. If it is > 5, round up. If it is exactly 5, and there are _any_ other digits (the + * remainder of "digits_" or "xbit_"), round up, else round to even. + * + * The current implementation stores 16 digits in "digits_" so that digits can be "pop"ped back + * out if needed during subtraction (negative addition) operations. + */ class Number::Guard { std::uint64_t digits_{0}; // 16 decimal guard digits @@ -171,7 +223,21 @@ class Number::Guard std::uint8_t sbit_ : 1 {0}; // the sign of the guard digits public: - explicit Guard() = default; + internalrep const minMantissa; + internalrep const maxMantissa; + MantissaRange::CuspRoundingFix const cuspRoundingFix; + + explicit Guard( + internalrep const& minMantissa, + internalrep const& maxMantissa, + MantissaRange::CuspRoundingFix cuspRoundingFix) + : minMantissa(minMantissa), maxMantissa(maxMantissa), cuspRoundingFix(cuspRoundingFix) + { + } + + explicit Guard(MantissaRange const& range) : Guard(range.min, range.max, range.cuspRoundingFix) + { + } // set & test the sign bit void @@ -194,40 +260,54 @@ public: unsigned pop() noexcept; - /** Drop a digit from the mantissa, and increment the exponent, storing the dropped digit in + // if true, there are no digits in the guard, including dropped digits (xbit_) + [[nodiscard]] bool + empty() const noexcept; + + /** + * Drop a digit from the mantissa, and increment the exponent, storing the dropped digit in * this Guard. * * Substitute for: - push(mantissa % 10); - mantissa /= 10; - ++exponent; + * push(mantissa % 10); + * mantissa /= 10; + * ++exponent; */ template void doDropDigit(T& mantissa, int& exponent) noexcept; + enum class Round { + // The result is exact. No rounding is needed. Only used if cuspRoundingFix is Enabled330 or + // higher. + Exact = -2, + // Round down. Since we use integer math, that usually means no change is needed. + // Exceptions are for when the result is between kMaxRep and kMaxRepUp (round to kMaxRep), + // or after subtraction where _any_ remainder will modify the result. The latter is what + // distinguishes Exact from Down. + Down = -1, + // The result was exactly half-way between two integers. This will round to even. + Even = 0, + // Round up. Always adds 1 (or subtracts 1 in some cases if cuspRoundingFix is not + // Enabled) + Up = 1, + }; + // Indicate round direction: 1 is up, -1 is down, 0 is even // This enables the client to round towards nearest, and on // tie, round towards even. - [[nodiscard]] int + [[nodiscard]] Round round() const noexcept; // Modify the result to the correctly rounded value template void - doRoundUp( - bool& negative, - T& mantissa, - int& exponent, - internalrep const& minMantissa, - internalrep const& maxMantissa, - MantissaRange::CuspRoundingFix cuspRoundingFixEnabled, - std::string location); + doRoundUp(bool& negative, T& mantissa, int& exponent, std::string location); // Modify the result to the correctly rounded value template void - doRoundDown(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa); + doRoundDown(bool& negative, T& mantissa, int& exponent); // Modify the result to the correctly rounded value void @@ -239,7 +319,7 @@ private: template void - bringIntoRange(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa); + bringIntoRange(bool& negative, T& mantissa, int& exponent); }; inline void @@ -289,6 +369,12 @@ Number::Guard::pop() noexcept return d; } +inline bool +Number::Guard::empty() const noexcept +{ + return digits_ == 0 && !xbit_; +} + template void Number::Guard::doDropDigit(T& mantissa, int& exponent) noexcept @@ -314,50 +400,52 @@ Number::Guard::doDropDigit(uint128_t& mantissa, int& exponent) noexce // -1 if Guard is less than half // 0 if Guard is exactly half // 1 if Guard is greater than half -int +Number::Guard::Round Number::Guard::round() const noexcept { - auto mode = Number::getround(); + // Local "mode" shadows and has the same value as the static thread_local "Number::mode". + // This ensures the overhead of loading the thread_local is only incurred once. + auto const mode = Number::getround(); + + if (cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330 && empty()) + { + // No remainder + return Round::Exact; + } if (mode == RoundingMode::TowardsZero) - return -1; + return Round::Down; - if (mode == RoundingMode::Downward) + // Also Towards Zero + if ((mode == RoundingMode::Downward && !sbit_) || (mode == RoundingMode::Upward && sbit_)) { - if (sbit_) - { - if (digits_ > 0 || xbit_) - return 1; - } - return -1; + return Round::Down; } - if (mode == RoundingMode::Upward) + // Away from Zero. Since we checked sbit_ in the previous block, we don't need to check it + // again. + if (mode == RoundingMode::Downward || mode == RoundingMode::Upward) { - if (sbit_) - return -1; - if (digits_ > 0 || xbit_) - return 1; - return -1; + if (empty()) + return Round::Down; + return Round::Up; } + XRPL_ASSERT( + mode == RoundingMode::ToNearest, "xrpl::Number::Guard::Round : fallthrough to ToNearest"); // assume round to nearest if mode is not one of the predefined values if (digits_ > 0x5000'0000'0000'0000) - return 1; + return Round::Up; if (digits_ < 0x5000'0000'0000'0000) - return -1; + return Round::Down; if (xbit_) - return 1; - return 0; + return Round::Up; + return Round::Even; } template void -Number::Guard::bringIntoRange( - bool& negative, - T& mantissa, - int& exponent, - internalrep const& minMantissa) +Number::Guard::bringIntoRange(bool& negative, T& mantissa, int& exponent) { // Bring mantissa back into the minMantissa / maxMantissa range AFTER // rounding @@ -378,22 +466,15 @@ Number::Guard::bringIntoRange( template void -Number::Guard::doRoundUp( - bool& negative, - T& mantissa, - int& exponent, - internalrep const& minMantissa, - internalrep const& maxMantissa, - MantissaRange::CuspRoundingFix cuspRoundingFixEnabled, - std::string location) +Number::Guard::doRoundUp(bool& negative, T& mantissa, int& exponent, std::string location) { auto r = round(); - if (r == 1 || (r == 0 && (mantissa & 1) == 1)) + if (r == Round::Up || (r == Round::Even && (mantissa & 1) == 1)) { - auto const safeToIncrement = [&maxMantissa](auto const& mantissa) { + auto const safeToIncrement = [this](auto const& mantissa) { return mantissa < maxMantissa && mantissa < kMaxRep; }; - if (cuspRoundingFixEnabled == MantissaRange::CuspRoundingFix::Enabled) + if (cuspRoundingFix != MantissaRange::CuspRoundingFix::Disabled) { // Ensure mantissa after incrementing fits within both the // min/maxMantissa range and is a valid "rep". @@ -414,14 +495,7 @@ Number::Guard::doRoundUp( safeToIncrement(mantissa), "xrpl::Number::Guard::doRoundUp", "can't recurse more than once"); - doRoundUp( - negative, - mantissa, - exponent, - minMantissa, - maxMantissa, - cuspRoundingFixEnabled, - location); + doRoundUp(negative, mantissa, exponent, location); return; } } @@ -440,30 +514,43 @@ Number::Guard::doRoundUp( } } } - bringIntoRange(negative, mantissa, exponent, minMantissa); + bringIntoRange(negative, mantissa, exponent); if (exponent > kMaxExponent) Throw(std::string(location)); } template void -Number::Guard::doRoundDown( - bool& negative, - T& mantissa, - int& exponent, - internalrep const& minMantissa) +Number::Guard::doRoundDown(bool& negative, T& mantissa, int& exponent) { auto r = round(); - if (r == 1 || (r == 0 && (mantissa & 1) == 1)) + if (cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330) { - --mantissa; - if (mantissa < minMantissa) + // If there was any remainder, subtract 1 from the result. This is sufficient to get the + // best rounding. + XRPL_ASSERT( + r == Round::Exact || mantissa > maxMantissa, + "xrpl::Number::Guard::doRoundDown : mantissa is expected size"); + if (r != Round::Exact) { - mantissa *= 10; - --exponent; + --mantissa; } } - bringIntoRange(negative, mantissa, exponent, minMantissa); + else + { + // Need to preserve the incorrect behavior until the fix amendment can be retired, + // because otherwise would risk an unplanned ledger fork. + if (r == Round::Up || (r == Round::Even && (mantissa & 1) == 1)) + { + --mantissa; + if (mantissa < minMantissa) + { + mantissa *= 10; + --exponent; + } + } + } + bringIntoRange(negative, mantissa, exponent); } // Modify the result to the correctly rounded value @@ -471,7 +558,7 @@ void Number::Guard::doRound(rep& drops, std::string location) const { auto r = round(); - if (r == 1 || (r == 0 && (drops & 1) == 1)) + if (r == Round::Up || (r == Round::Even && (drops & 1) == 1)) { if (drops >= kMaxRep) { @@ -530,7 +617,7 @@ doNormalize( int& exponent, MantissaRange::rep const& minMantissa, MantissaRange::rep const& maxMantissa, - MantissaRange::CuspRoundingFix cuspRoundingFixEnabled, + MantissaRange::CuspRoundingFix cuspRoundingFix, bool dropped) { static constexpr auto kMinExponent = Number::kMinExponent; @@ -553,7 +640,7 @@ doNormalize( m *= 10; --exponent; } - Guard g; + Guard g(minMantissa, maxMantissa, cuspRoundingFix); if (negative) g.setNegative(); if (dropped) @@ -598,14 +685,7 @@ doNormalize( XRPL_ASSERT_PARTS(m <= kMaxRep, "xrpl::doNormalize", "intermediate mantissa fits in int64"); mantissa = m; - g.doRoundUp( - negative, - mantissa, - exponent, - minMantissa, - maxMantissa, - cuspRoundingFixEnabled, - "Number::normalize 2"); + g.doRoundUp(negative, mantissa, exponent, "Number::normalize 2"); XRPL_ASSERT_PARTS( mantissa >= minMantissa && mantissa <= maxMantissa, "xrpl::doNormalize", @@ -620,13 +700,12 @@ Number::normalize( int& exponent, internalrep const& minMantissa, internalrep const& maxMantissa, - MantissaRange::CuspRoundingFix cuspRoundingFixEnabled) + MantissaRange::CuspRoundingFix cuspRoundingFix) { // Not used by every compiler version, and thus not necessarily // counted by coverage build // LCOV_EXCL_START - doNormalize( - negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFixEnabled, false); + doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFix, false); // LCOV_EXCL_STOP } @@ -638,13 +717,12 @@ Number::normalize( int& exponent, internalrep const& minMantissa, internalrep const& maxMantissa, - MantissaRange::CuspRoundingFix cuspRoundingFixEnabled) + MantissaRange::CuspRoundingFix cuspRoundingFix) { // Not used by every compiler version, and thus not necessarily // counted by coverage build // LCOV_EXCL_START - doNormalize( - negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFixEnabled, false); + doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFix, false); // LCOV_EXCL_STOP } @@ -656,16 +734,27 @@ Number::normalize( int& exponent, internalrep const& minMantissa, internalrep const& maxMantissa, - MantissaRange::CuspRoundingFix cuspRoundingFixEnabled) + MantissaRange::CuspRoundingFix cuspRoundingFix) { - doNormalize( - negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFixEnabled, false); + doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFix, false); } void Number::normalize(MantissaRange const& range) { - normalize(negative_, mantissa_, exponent_, range.min, range.max, range.cuspRoundingFixEnabled); + normalize(negative_, mantissa_, exponent_, range.min, range.max, range.cuspRoundingFix); +} + +void +Number::normalize(Guard const& guard) +{ + normalize( + negative_, + mantissa_, + exponent_, + guard.minMantissa, + guard.maxMantissa, + guard.cuspRoundingFix); } // Copy the number, but set a new exponent. Because the mantissa doesn't change, @@ -719,46 +808,102 @@ Number::operator+=(Number const& y) bool const yn = y.negative_; uint128_t ym = y.mantissa_; auto ye = y.exponent_; - Guard g; + Guard g(kRange); + + auto const& minMantissa = g.minMantissa; + auto const& maxMantissa = g.maxMantissa; + auto const cuspRoundingFix = g.cuspRoundingFix; + + // Bring the exponents of both values into agreement, so the mantissas are on the same scale + // and can be added directly together. + + auto const upperLimit = static_cast(g.minMantissa) * 1000; + // For the "adjust" lambda + // expandM / expandE: The values for which the mantissa will be expanded, and the exponent + // decreased to match. Mantissa won't be expanded beyond upperLimit. + // (37e8 == 37000e5 == 37000000e2) + // shrinkM / shrinkE: The values for which the mantissa will be shrunk, and exponent increased + // to match, if necessary. + auto const adjust = [&g, &upperLimit]( + uint128_t& expandM, int& expandE, uint128_t& shrinkM, int& shrinkE) { + // Adjust up and down until the exponents match + if (g.cuspRoundingFix == MantissaRange::CuspRoundingFix::Enabled330) + { + // For Enabled330, there are three steps. + // 1. First, shrink the mantissa of shrinkM/shrinkE while shrinkM ends in 0. + while (shrinkE < expandE && shrinkM % 10 == 0) + { + g.doDropDigit(shrinkM, shrinkE); + } + + // 2. Then expand the mantissa of expandM/expandE, with a limit for expandM a few orders + // of magnitude above the MantissaRange. This will leave a few extra digits for rounding + // later, but nothing excessive. + while (shrinkE < expandE && expandE > kMinExponent && expandM < upperLimit) + { + expandM *= 10; + --expandE; + } + } + + // 3. Finally, shrink the mantissa of shrinkM/shrinkE until the exponents match. Any removed + // digits will be put into the Guard. This is the only step for non-Enabled330 modes. + while (shrinkE < expandE) + { + g.doDropDigit(shrinkM, shrinkE); + } + }; + + // Shrink the mantissa and raise the exponent of the value with the lower exponent. Store any + // dropped digits in the Guard. if (xe < ye) { if (xn) g.setNegative(); - do - { - g.doDropDigit(xm, xe); - } while (xe < ye); + + adjust(ym, ye, xm, xe); } else if (xe > ye) { if (yn) g.setNegative(); - do - { - g.doDropDigit(ym, ye); - } while (xe > ye); - } - auto const& range = kRange.get(); - auto const& minMantissa = range.min; - auto const& maxMantissa = range.max; - auto const cuspRoundingFixEnabled = range.cuspRoundingFixEnabled; + adjust(xm, xe, ym, ye); + } + else if (g.cuspRoundingFix == MantissaRange::CuspRoundingFix::Enabled330) + { + // Both values have the same exponent. + // Set the sign of the Guard based on the sign of the Number with the smallest + // unsigned _mantissa_ + if ((xm < ym && xn) || (ym < xm && yn)) + g.setNegative(); + } if (xn == yn) { xm += ym; - if (xm > maxMantissa || xm > kMaxRep) + + if (g.cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330) { - g.doDropDigit(xm, xe); + // Don't do any adjustments for Enabled330. Normalize will take care of it + // Because of "adjust", the only way there can be data in the Guard is if we first grew + // the mantissa past the maxMantissa. Since we added here, it can only get bigger. + // If xm > maxMantissa, then doNormalize has all the data it needs from the last 3-4 + // digits, plus the "dropped" flag that will be passed in. + // If not, then the mantissa will only need to be padded out with 0s and won't need to + // round. + XRPL_ASSERT( + xm > maxMantissa || g.empty(), + "xrpl::Number::operator+ : rounding state expected after add"); + } + else + { + if (xm > maxMantissa || xm > kMaxRep) + { + g.doDropDigit(xm, xe); + } + g.doRoundUp(xn, xm, xe, "Number::addition overflow"); } - g.doRoundUp( - xn, - xm, - xe, - minMantissa, - maxMantissa, - cuspRoundingFixEnabled, - "Number::addition overflow"); } else { @@ -772,19 +917,67 @@ Number::operator+=(Number const& y) xe = ye; xn = yn; } - while (xm < minMantissa && xm * 10 <= kMaxRep) + if (cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330) { - xm *= 10; - xm -= g.pop(); - --xe; + // Because we subtracted, xm can have any number of digits from 1 up to + // upperLimit * 10, and g can be in any state. (Note that xm can't be zero, because that + // special case was tested earlier.) + + // Grow xm/xe and pull digits out of the Guard until xm reaches upperLimit, but stop if + // the Guard empties out, because no rounding will be necessary. This will ensure that + // normalize will have enough information to make an accurate rounding decision. + // (Normalize will pad a small mantissa back into range.) Note that if any digits were + // lost (xbit_), the Guard will never be empty, so xm will grow larger than upperLimit. + while (xm < upperLimit && !g.empty()) + { + xm *= 10; + xm -= g.pop(); + --xe; + } + XRPL_ASSERT( + xm > maxMantissa || g.empty(), + "xrpl::Number::operator+ : rounding state expected after subtract"); } - g.doRoundDown(xn, xm, xe, minMantissa); + else + { + // Grow xm/xe and pull digits out of the Guard until it's back in the + // minMantissa/maxMantissa range. + while (xm < minMantissa && xm * 10 <= kMaxRep) + { + xm *= 10; + xm -= g.pop(); + --xe; + } + } + // Rounding down can result in decrementing xm, based on whether there is any data left in + // the Guard (depending on cuspRoundingFix). Note that if that happens, then the Guard is + // not empty. For Enabled330, that will also result in the "dropped" flag being passed to + // doNormalize, which may result in the mantissa being incremented again. It doesn't matter + // what the dropped digits are, only that they exist. This is because subtracting one + // "overcorrects", so we know there are still trailing digits to be accounted for in the + // rounding. + // + // This works because + // 1. The rounding up will be done _after_ the mantissa is brought into range. It may not + // be in range right now, and + // 2. The "dropped" flag is only ever used as a tie-breaker, specifically when rounding + // away from zero, and the dropped digits are 0, or when rounding to nearest, and + // the dropped digits represent exactly 0.5. + g.doRoundDown(xn, xm, xe); } + doNormalize( + xn, + xm, + xe, + minMantissa, + maxMantissa, + cuspRoundingFix, + cuspRoundingFix == MantissaRange::CuspRoundingFix::Enabled330 && !g.empty()); negative_ = xn; mantissa_ = static_cast(xm); exponent_ = xe; - normalize(range); + XRPL_ASSERT(isnormal(), "xrpl::Number::operator+= : result is normal"); return *this; } @@ -818,14 +1011,11 @@ Number::operator*=(Number const& y) auto ze = xe + ye; auto zs = xs * ys; bool zn = (zs == -1); - Guard g; + Guard g(kRange); if (zn) g.setNegative(); - auto const& range = kRange.get(); - auto const& minMantissa = range.min; - auto const& maxMantissa = range.max; - auto const cuspRoundingFixEnabled = range.cuspRoundingFixEnabled; + auto const& maxMantissa = g.maxMantissa; while (zm > maxMantissa || zm > kMaxRep) { @@ -834,19 +1024,12 @@ Number::operator*=(Number const& y) xm = static_cast(zm); xe = ze; - g.doRoundUp( - zn, - xm, - xe, - minMantissa, - maxMantissa, - cuspRoundingFixEnabled, - "Number::multiplication overflow : exponent is " + std::to_string(xe)); + g.doRoundUp(zn, xm, xe, "Number::multiplication overflow : exponent is " + std::to_string(xe)); negative_ = zn; mantissa_ = xm; exponent_ = xe; - normalize(range); + normalize(g); return *this; } @@ -882,7 +1065,7 @@ Number::operator/=(Number const& y) auto const& range = kRange.get(); auto const& minMantissa = range.min; auto const& maxMantissa = range.max; - auto const cuspRoundingFixEnabled = range.cuspRoundingFixEnabled; + auto const cuspRoundingFix = range.cuspRoundingFix; // Division operates on two large integers (16-digit for small // mantissas, 19-digit for large) using integer math. If the values @@ -930,7 +1113,7 @@ Number::operator/=(Number const& y) // This is equivalent to if we had used an initial factor of 10^22, // a couple digits more than we actually need. // - // Stage 3: If there is still a remainder, and the CuspRoundingFix + // Stage 3: If there is still a remainder, and the cuspRoundingFix // is enabled, pass a flag indicating such to doNormalize. The Guard // in doNormalize will treat that flag as if non-zero digits had // been dropped from the mantissa when shrinking it into range. @@ -1014,14 +1197,14 @@ Number::operator/=(Number const& y) // rounding fix is enabled, flag if there is still // a remainder from stage 2. bool const useTrailingRemainder = - cuspRoundingFixEnabled == MantissaRange::CuspRoundingFix::Enabled; + cuspRoundingFix != MantissaRange::CuspRoundingFix::Disabled; if (useTrailingRemainder) { dropped = partialNumerator % dm != 0; } } } - doNormalize(zp, zm, ze, minMantissa, maxMantissa, cuspRoundingFixEnabled, dropped); + doNormalize(zp, zm, ze, minMantissa, maxMantissa, cuspRoundingFix, dropped); negative_ = zp; mantissa_ = static_cast(zm); exponent_ = ze; @@ -1035,7 +1218,7 @@ operator rep() const { rep drops = mantissa(); int offset = exponent(); - Guard g; + Guard g(kRange); if (drops != 0) { if (negative_) diff --git a/src/libxrpl/basics/ResolverAsio.cpp b/src/libxrpl/basics/ResolverAsio.cpp index 7e1b56f87a..25e95b7fc5 100644 --- a/src/libxrpl/basics/ResolverAsio.cpp +++ b/src/libxrpl/basics/ResolverAsio.cpp @@ -33,10 +33,11 @@ namespace xrpl { -/** Mix-in to track when all pending I/O is complete. - Derived classes must be callable with this signature: - void asyncHandlersComplete() -*/ +/** + * Mix-in to track when all pending I/O is complete. + * Derived classes must be callable with this signature: + * void asyncHandlersComplete() + */ template class AsyncObject { @@ -51,10 +52,11 @@ public: XRPL_ASSERT(pending_.load() == 0, "xrpl::AsyncObject::~AsyncObject : nothing pending"); } - /** RAII container that maintains the count of pending I/O. - Bind this into the argument list of every handler passed - to an initiating function. - */ + /** + * RAII container that maintains the count of pending I/O. + * Bind this into the argument list of every handler passed + * to an initiating function. + */ class CompletionCounter { public: diff --git a/src/libxrpl/basics/base64.cpp b/src/libxrpl/basics/base64.cpp index 541ddd0839..c980a08669 100644 --- a/src/libxrpl/basics/base64.cpp +++ b/src/libxrpl/basics/base64.cpp @@ -76,32 +76,37 @@ getInverse() return &kTab[0]; } -/// Returns max chars needed to encode a base64 string +/** + * Returns max chars needed to encode a base64 string + */ constexpr std::size_t encodedSize(std::size_t n) { return 4 * ((n + 2) / 3); } -/// Returns max bytes needed to decode a base64 string +/** + * Returns max bytes needed to decode a base64 string + */ constexpr std::size_t decodedSize(std::size_t n) { return ((n / 4) * 3) + 2; } -/** Encode a series of octets as a padded, base64 string. - - The resulting string will not be null terminated. - - @par Requires - - The memory pointed to by `out` points to valid memory - of at least `encoded_size(len)` bytes. - - @return The number of characters written to `out`. This - will exclude any null termination. -*/ +/** + * Encode a series of octets as a padded, base64 string. + * + * The resulting string will not be null terminated. + * + * @par Requires + * + * The memory pointed to by `out` points to valid memory + * of at least `encoded_size(len)` bytes. + * + * @return The number of characters written to `out`. This + * will exclude any null termination. + */ std::size_t encode(void* dest, void const* src, std::size_t len) { @@ -142,17 +147,18 @@ encode(void* dest, void const* src, std::size_t len) return out - static_cast(dest); } -/** Decode a padded base64 string into a series of octets. - - @par Requires - - The memory pointed to by `out` points to valid memory - of at least `decoded_size(len)` bytes. - - @return The number of octets written to `out`, and - the number of characters read from the input string, - expressed as a pair. -*/ +/** + * Decode a padded base64 string into a series of octets. + * + * @par Requires + * + * The memory pointed to by `out` points to valid memory + * of at least `decoded_size(len)` bytes. + * + * @return The number of octets written to `out`, and + * the number of characters read from the input string, + * expressed as a pair. + */ std::pair decode(void* dest, char const* src, std::size_t len) { diff --git a/src/libxrpl/basics/make_SSLContext.cpp b/src/libxrpl/basics/make_SSLContext.cpp index 165d36076b..3fca0c0d77 100644 --- a/src/libxrpl/basics/make_SSLContext.cpp +++ b/src/libxrpl/basics/make_SSLContext.cpp @@ -30,35 +30,37 @@ namespace xrpl { namespace openssl::detail { -/** The default strength of self-signed RSA certificates. - - Per NIST Special Publication 800-57 Part 3, 2048-bit RSA is still - considered acceptably secure. Generally, we would want to go above - and beyond such recommendations (e.g. by using 3072 or 4096 bits) - but there is a computational cost associated with that may not - be worth paying, considering that: - - - We regenerate a new ephemeral certificate and a securely generated - random private key every time the server is started; and - - There should not be any truly secure information (e.g. seeds or private - keys) that gets relayed to the server anyways over these RPCs. - - @note If you increase the number of bits you need to generate new - default DH parameters and update defaultDH accordingly. - * */ +/** + * The default strength of self-signed RSA certificates. + * + * Per NIST Special Publication 800-57 Part 3, 2048-bit RSA is still + * considered acceptably secure. Generally, we would want to go above + * and beyond such recommendations (e.g. by using 3072 or 4096 bits) + * but there is a computational cost associated with that may not + * be worth paying, considering that: + * + * - We regenerate a new ephemeral certificate and a securely generated + * random private key every time the server is started; and + * - There should not be any truly secure information (e.g. seeds or private + * keys) that gets relayed to the server anyways over these RPCs. + * + * @note If you increase the number of bits you need to generate new + * default DH parameters and update defaultDH accordingly. + */ int gDefaultRsaKeyBits = 2048; -/** The default DH parameters. - - These were generated using the OpenSSL command: `openssl dhparam 2048` - by Nik Bougalis on May, 29, 2022. - - It is safe to use this, but if you want you can generate different - parameters and put them here. There's no easy way to change this - via the config file at this time. - - @note If you increase the number of bits you need to update - defaultRSAKeyBits accordingly. +/** + * The default DH parameters. + * + * These were generated using the OpenSSL command: `openssl dhparam 2048` + * by Nik Bougalis on May, 29, 2022. + * + * It is safe to use this, but if you want you can generate different + * parameters and put them here. There's no easy way to change this + * via the config file at this time. + * + * @note If you increase the number of bits you need to update + * defaultRSAKeyBits accordingly. */ static constexpr char kDefaultDh[] = "-----BEGIN DH PARAMETERS-----\n" @@ -70,19 +72,20 @@ static constexpr char kDefaultDh[] = "9yqY3xXZID240RRcaJ25+U4lszFPqP+CEwIBAg==\n" "-----END DH PARAMETERS-----"; -/** The default list of ciphers we accept over TLS. - - Generally we include cipher suites that are part of TLS v1.2, but - we specifically exclude: - - - the DSS cipher suites (!DSS); - - cipher suites using pre-shared keys (!PSK); - - cipher suites that don't offer encryption (!eNULL); and - - cipher suites that don't offer authentication (!aNULL). - - @note Server administrators can override this default list, on either a - global or per-port basis, using the `ssl_ciphers` directive in the - config file. +/** + * The default list of ciphers we accept over TLS. + * + * Generally we include cipher suites that are part of TLS v1.2, but + * we specifically exclude: + * + * - the DSS cipher suites (!DSS); + * - cipher suites using pre-shared keys (!PSK); + * - cipher suites that don't offer encryption (!eNULL); and + * - cipher suites that don't offer authentication (!aNULL). + * + * @note Server administrators can override this default list, on either a + * global or per-port basis, using the `ssl_ciphers` directive in the + * config file. */ std::string const kDefaultCipherList = "TLSv1.2:!CBC:!DSS:!PSK:!eNULL:!aNULL"; diff --git a/src/libxrpl/core/detail/LoadMonitor.cpp b/src/libxrpl/core/detail/LoadMonitor.cpp index 95e0e7d3b9..92eff61285 100644 --- a/src/libxrpl/core/detail/LoadMonitor.cpp +++ b/src/libxrpl/core/detail/LoadMonitor.cpp @@ -101,10 +101,11 @@ LoadMonitor::addLoadSample(LoadEvent const& s) addSamples(1, latency); } -/* Add multiple samples - @param count The number of samples to add - @param latencyMS The total number of milliseconds -*/ +/** + * Add multiple samples + * @param count The number of samples to add + * @param latencyMS The total number of milliseconds + */ void LoadMonitor::addSamples(int count, std::chrono::milliseconds latency) { diff --git a/src/libxrpl/crypto/RFC1751.cpp b/src/libxrpl/crypto/RFC1751.cpp index 41e29ee00c..4b17e1443c 100644 --- a/src/libxrpl/crypto/RFC1751.cpp +++ b/src/libxrpl/crypto/RFC1751.cpp @@ -379,14 +379,15 @@ RFC1751::etob(std::string& strData, std::vector vsHuman) return 1; } -/** Convert words separated by spaces into a 128 bit key in big-endian format. - - @return - 1 if succeeded - 0 if word not in dictionary - -1 if badly formed string - -2 if words are okay but parity is wrong. -*/ +/** + * Convert words separated by spaces into a 128 bit key in big-endian format. + * + * @return + * 1 if succeeded + * 0 if word not in dictionary + * -1 if badly formed string + * -2 if words are okay but parity is wrong. + */ int RFC1751::getKeyFromEnglish(std::string& strKey, std::string const& strHuman) { @@ -415,7 +416,8 @@ RFC1751::getKeyFromEnglish(std::string& strKey, std::string const& strHuman) return rc; } -/** Convert to human from a 128 bit key in big-endian format +/** + * Convert to human from a 128 bit key in big-endian format */ void RFC1751::getEnglishFromKey(std::string& strHuman, std::string const& strKey) diff --git a/src/libxrpl/json/Writer.cpp b/src/libxrpl/json/Writer.cpp index c5c4b3cd31..4c922a0e33 100644 --- a/src/libxrpl/json/Writer.cpp +++ b/src/libxrpl/json/Writer.cpp @@ -199,15 +199,21 @@ private: // JSON collections are either arrays, or objects. struct Collection { - /** What type of collection are we in? */ + /** + * What type of collection are we in? + */ Writer::CollectionType type = Writer::CollectionType::Array; - /** Is this the first entry in a collection? - * If false, we have to emit a , before we write the next entry. */ + /** + * Is this the first entry in a collection? + * If false, we have to emit a , before we write the next entry. + */ bool isFirst = true; #ifndef NDEBUG - /** What tags have we already seen in this collection? */ + /** + * What tags have we already seen in this collection? + */ std::set tags{}; // NOLINT(readability-redundant-member-init) #endif }; @@ -230,9 +236,8 @@ Writer::~Writer() impl_->finishAll(); } -Writer::Writer(Writer&& w) noexcept +Writer::Writer(Writer&& w) noexcept : impl_(std::move(w.impl_)) { - impl_ = std::move(w.impl_); } Writer& diff --git a/src/libxrpl/json/json_reader.cpp b/src/libxrpl/json/json_reader.cpp index a12ec7b565..f9134e6629 100644 --- a/src/libxrpl/json/json_reader.cpp +++ b/src/libxrpl/json/json_reader.cpp @@ -5,12 +5,13 @@ #include #include +#include #include #include -#include #include #include #include +#include namespace json { // Implementation of class Reader @@ -605,34 +606,17 @@ bool Reader::decodeDouble(Token& token) { double value = 0; - int const bufferSize = 32; - int count = 0; - int const length = int(token.end - token.start); - // Sanity check to avoid buffer overflow exploits. - if (length < 0) - { - return addError("Unable to parse token length", token); - } - // Avoid using a string constant for the format control string given to - // sscanf, as this can cause hard to debug crashes on OS X. See here for - // more info: - // - // http://developer.apple.com/library/mac/#DOCUMENTATION/DeveloperTools/gcc-4.0.1/gcc/Incompatibilities.html - char format[] = "%lf"; - if (length <= bufferSize) - { - Char buffer[bufferSize + 1]; - memcpy(buffer, token.start, length); - buffer[length] = 0; - count = sscanf(buffer, format, &value); - } - else - { - std::string const buffer(token.start, token.end); - count = sscanf(buffer.c_str(), format, &value); - } - if (count != 1) + auto const [ptr, ec] = std::from_chars(token.start, token.end, value); + + // Reject anything from_chars could not turn into a finite double: + // - ec != std::errc{}: no valid conversion, or an out-of-range magnitude + // (e.g. 1e400). + // - ptr != token.end: readNumber() is permissive about which characters + // it collects into a token (it will, for example, keep a '+' mid-token), + // but from_chars() will stop at the first character it cannot parse. + if (ec != std::errc{} || ptr != token.end) return addError("'" + std::string(token.start, token.end) + "' is not a number.", token); + currentValue() = value; return true; } diff --git a/src/libxrpl/json/json_value.cpp b/src/libxrpl/json/json_value.cpp index c679e30e16..cf95d873fb 100644 --- a/src/libxrpl/json/json_value.cpp +++ b/src/libxrpl/json/json_value.cpp @@ -48,6 +48,7 @@ public: if (length == kUnknown) length = (value != nullptr) ? (unsigned int)strlen(value) : 0; + // NOLINTNEXTLINE(cppcoreguidelines-no-malloc) char* newString = static_cast(malloc(length + 1)); if (value != nullptr) memcpy(newString, value, length); @@ -59,7 +60,10 @@ public: releaseStringValue(char* value) override { if (value != nullptr) + { + // NOLINTNEXTLINE(cppcoreguidelines-no-malloc) free(value); + } } }; @@ -120,7 +124,10 @@ Value::CZString::CZString(CZString const& other) Value::CZString::~CZString() { if ((cstr_ != nullptr) && index_ == static_cast(DuplicationPolicy::Duplicate)) + { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) valueAllocator()->releaseMemberName(const_cast(cstr_)); + } } bool @@ -167,7 +174,8 @@ Value::CZString::isStaticString() const // ////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////// -/*! \internal Default constructor initialization must be equivalent to: +/** + * @internal Default constructor initialization must be equivalent to: * memset( this, 0, sizeof(Value) ) * This optimization is used in ValueInternalMap fast allocator. */ @@ -241,6 +249,7 @@ Value::Value(std::string const& value) : type_(ValueType::String), allocated_(tr Value::Value(StaticString const& value) : type_(ValueType::String) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) value_.stringVal = const_cast(value.cStr()); } @@ -786,7 +795,9 @@ Value::isConvertibleTo(ValueType other) const return false; // unreachable; } -/// Number of values in array or object +/** + * Number of values in array or object + */ Value::UInt Value::size() const { diff --git a/src/libxrpl/json/json_writer.cpp b/src/libxrpl/json/json_writer.cpp index c9f8cb688b..fdfa5d3cf0 100644 --- a/src/libxrpl/json/json_writer.cpp +++ b/src/libxrpl/json/json_writer.cpp @@ -4,13 +4,14 @@ #include #include -#include +#include #include #include #include #include #include #include +#include #include namespace json { @@ -76,19 +77,18 @@ valueToString(UInt value) std::string valueToString(double value) { - // Allocate a buffer that is more than large enough to store the 16 digits - // of precision requested below. + // Format with 16 significant digits. + // We need not request the alternative representation that always has a + // decimal point because JSON doesn't distinguish the concepts of reals and integers. + // A double never needs more than 32 characters in this form, + // so to_chars cannot actually run out of room here. char buffer[32]; - // Print into the buffer. We need not request the alternative representation - // that always has a decimal point because JSON doesn't distinguish the - // concepts of reals and integers. -#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) // Use secure version with visual studio 2005 - // to avoid warning. - sprintf_s(buffer, sizeof(buffer), "%.16g", value); -#else - snprintf(buffer, sizeof(buffer), "%.16g", value); -#endif - return buffer; + auto const [ptr, ec] = + std::to_chars(buffer, buffer + sizeof(buffer), value, std::chars_format::general, 16); + XRPL_ASSERT(ec == std::errc{}, "json::valueToString(double) : conversion fits buffer"); + if (ec != std::errc{}) + return {}; + return std::string(buffer, ptr); } std::string diff --git a/src/libxrpl/ledger/PaymentSandbox.cpp b/src/libxrpl/ledger/PaymentSandbox.cpp index 97e3e53cbf..a7a8962095 100644 --- a/src/libxrpl/ledger/PaymentSandbox.cpp +++ b/src/libxrpl/ledger/PaymentSandbox.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -170,7 +171,7 @@ DeferredCredits::issuerSelfDebitMPT( } void -DeferredCredits::ownerCount(AccountID const& id, std::uint32_t cur, std::uint32_t next) +DeferredCredits::ownerCount(AccountID const& id, OwnerCounts const& cur, OwnerCounts const& next) { auto const v = std::max(cur, next); auto r = ownerCounts_.emplace(id, v); @@ -181,7 +182,7 @@ DeferredCredits::ownerCount(AccountID const& id, std::uint32_t cur, std::uint32_ } } -std::optional +std::optional DeferredCredits::ownerCount(AccountID const& id) const { auto i = ownerCounts_.find(id); @@ -391,10 +392,10 @@ PaymentSandbox::balanceHookSelfIssueMPT(xrpl::MPTIssue const& issue, std::int64_ return STAmount{issue}; } -std::uint32_t -PaymentSandbox::ownerCountHook(AccountID const& account, std::uint32_t count) const +OwnerCounts +PaymentSandbox::ownerCountHook(AccountID const& account, OwnerCounts const& count) const { - std::uint32_t result = count; + OwnerCounts result = count; for (auto curSB = this; curSB != nullptr; curSB = curSB->ps_) { if (auto adj = curSB->tab_.ownerCount(account)) @@ -442,8 +443,8 @@ PaymentSandbox::issuerSelfDebitHookMPT( void PaymentSandbox::adjustOwnerCountHook( AccountID const& account, - std::uint32_t cur, - std::uint32_t next) + OwnerCounts const& cur, + OwnerCounts const& next) { tab_.ownerCount(account, cur, next); } diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index ebac76a754..8116f4f641 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -431,8 +432,7 @@ canWithdraw(ReadView const& view, STTx const& tx) TER doWithdraw( - ApplyView& view, - STTx const& tx, + ApplyViewContext ctx, AccountID const& senderAcct, AccountID const& dstAcct, AccountID const& sourceAcct, @@ -440,23 +440,24 @@ doWithdraw( STAmount const& amount, beast::Journal j) { + auto const dstSle = ctx.view.read(keylet::account(dstAcct)); + // Create trust line or MPToken for the receiving account if (dstAcct == senderAcct) { - if (auto const ter = addEmptyHolding(view, senderAcct, priorBalance, amount.asset(), j); + if (auto const ter = addEmptyHolding(ctx, senderAcct, priorBalance, amount.asset(), j); !isTesSuccess(ter) && ter != tecDUPLICATE) return ter; } else { - auto dstSle = view.read(keylet::account(dstAcct)); - if (auto err = verifyDepositPreauth(tx, view, senderAcct, dstAcct, dstSle, j)) + if (auto err = verifyDepositPreauth(ctx.tx, ctx.view, senderAcct, dstAcct, dstSle, j)) return err; } // Sanity check if (accountHolds( - view, + ctx.view, sourceAcct, amount.asset(), FreezeHandling::IgnoreFreeze, @@ -469,9 +470,18 @@ doWithdraw( // LCOV_EXCL_STOP } + // A reserve sponsor only covers tx.Account's own objects, so resolve the + // sponsor against the destination. accountSend can auto-create a holding + // for dstAcct; keying on the destination ensures a third-party destination's + // holding is never stamped with the tx's reserve sponsor. + auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, dstSle); + if (!sponsorSle) + return sponsorSle.error(); // LCOV_EXCL_LINE + // Move the funds directly from the broker's pseudo-account to the // dstAcct - return accountSend(view, sourceAcct, dstAcct, amount, j, WaiveTransferFee::Yes); + return accountSend( + ctx.view, sourceAcct, dstAcct, amount, j, *sponsorSle, WaiveTransferFee::Yes); } TER diff --git a/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp b/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp index 1c4acc7bc4..faca4ebfb6 100644 --- a/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp +++ b/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp @@ -7,7 +7,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -15,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -41,46 +44,226 @@ isGlobalFrozen(ReadView const& view, AccountID const& issuer) return false; } +namespace { + // An owner count cannot be negative. If adjustment would cause a negative // owner count, clamp the owner count at 0. Similarly for overflow. This // adjustment allows the ownerCount to be adjusted up or down in multiple steps. // If id != std::nullopt, then do error reporting. // // Returns adjusted owner count. -static std::uint32_t +std::uint32_t confineOwnerCount( - std::uint32_t current, - std::int32_t adjustment, + std::uint32_t currentOwnerCount, + std::int32_t ownerCountAdj, std::optional const& id = std::nullopt, beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) { - std::uint32_t adjusted{current + adjustment}; - if (adjustment > 0) + std::uint32_t totalOwnerCount{currentOwnerCount + ownerCountAdj}; + if (ownerCountAdj > 0) { // Overflow is well defined on unsigned - if (adjusted < current) + if (totalOwnerCount < currentOwnerCount) { + // LCOV_EXCL_START if (id) { JLOG(j.fatal()) << "Account " << *id << " owner count exceeds max!"; } - adjusted = std::numeric_limits::max(); + totalOwnerCount = std::numeric_limits::max(); + // LCOV_EXCL_STOP } } else { // Underflow is well defined on unsigned - if (adjusted > current) + if (totalOwnerCount > currentOwnerCount) { + // LCOV_EXCL_START if (id) { JLOG(j.fatal()) << "Account " << *id << " owner count set below 0!"; } - adjusted = 0; + totalOwnerCount = 0; XRPL_ASSERT(!id, "xrpl::confineOwnerCount : id is not set"); + // LCOV_EXCL_STOP } } - return adjusted; + return totalOwnerCount; +} + +// Returns the number of account reserves funded by this account: 1 for itself (0 if sponsored by +// another account) plus the count of accounts it sponsors. +std::uint32_t +accountCountImpl(SLE::const_ref sle, std::int32_t accountCountAdj, beast::Journal j) +{ + bool const isSponsored = sle->isFieldPresent(sfSponsor); + std::int64_t const sponsoringAccountCount = sle->getFieldU32(sfSponsoringAccountCount); + std::int64_t const currentAccountCount = (isSponsored ? 0 : 1) + sponsoringAccountCount; + + std::int64_t totalAccountCount{currentAccountCount + accountCountAdj}; + if (totalAccountCount > std::numeric_limits::max()) + { + // LCOV_EXCL_START + JLOG(j.fatal()) << "Reserve count exceeds max!"; + totalAccountCount = std::numeric_limits::max(); + // LCOV_EXCL_STOP + } + else if (totalAccountCount < 0) + { + // LCOV_EXCL_START + UNREACHABLE("xrpl::accountCountImpl : Reserve count set below 0"); + JLOG(j.fatal()) << "Reserve count set below 0"; + totalAccountCount = 0; + // LCOV_EXCL_STOP + } + + return totalAccountCount; +} + +std::uint32_t +adjustOwnerCountImpl( + ApplyView& view, + SLE::ref sle, + SF_UINT32 const& sfield, + AccountID const& accID, + std::int32_t ownerCountAdj, + beast::Journal j) +{ + std::uint32_t const currentOwnerCount = sle->at(sfield); + std::uint32_t const totalOwnerCount = + confineOwnerCount(currentOwnerCount, ownerCountAdj, accID, j); + sle->at(sfield) = totalOwnerCount; + view.update(sle); + return totalOwnerCount; +} + +void +adjustOwnerCountSigned( + ApplyView& view, + SLE::ref accountSle, + SLE::ref sponsorSle, + std::int32_t adjustment, + beast::Journal j) +{ + if (view.rules().enabled(featureSponsor)) + { + XRPL_ASSERT(accountSle, "xrpl::adjustOwnerCountSigned : valid account sle"); + if (!accountSle) + return; // LCOV_EXCL_LINE + + auto const accountID = accountSle->getAccountID(sfAccount); + bool const validType = accountSle->getType() == ltACCOUNT_ROOT; + XRPL_ASSERT(validType, "xrpl::adjustOwnerCountSigned : valid account sle type"); + if (!validType) + return; // LCOV_EXCL_LINE + + XRPL_ASSERT(adjustment, "xrpl::adjustOwnerCountSigned : nonzero adjustment input"); + + OwnerCounts const currentOwnerCount(accountSle); + OwnerCounts totalOwnerCount(currentOwnerCount); + + if (sponsorSle) + { + bool const validSponsorType = sponsorSle->getType() == ltACCOUNT_ROOT; + XRPL_ASSERT(validSponsorType, "xrpl::adjustOwnerCountSigned : valid sponsor sle type"); + if (!validSponsorType) + return; // LCOV_EXCL_LINE + auto const sponsorID = sponsorSle->getAccountID(sfAccount); + + totalOwnerCount.sponsored = adjustOwnerCountImpl( + view, accountSle, sfSponsoredOwnerCount, accountID, adjustment, j); + + { + OwnerCounts const sponsorCurrent(sponsorSle); + OwnerCounts sponsorAdjustment(sponsorCurrent); + sponsorAdjustment.sponsoring = adjustOwnerCountImpl( + view, sponsorSle, sfSponsoringOwnerCount, sponsorID, adjustment, j); + view.adjustOwnerCountHook(sponsorID, sponsorCurrent, sponsorAdjustment); + } + + auto sponsorshipSle = view.peek(keylet::sponsorship(sponsorID, accountID)); + if (sponsorshipSle && adjustment > 0) + { + // Only decrease the pre-funded ReserveCount on Sponsorship if we assign new + // objects. Removing/reassigning ownership of the object doesn't increase + // RemainingOwnerCount back. Don't call hook because this counter is not something + // that requires reserve (like other sf...OwnerCounts do). + adjustOwnerCountImpl( + view, sponsorshipSle, sfRemainingOwnerCount, sponsorID, -adjustment, j); + } + } + + totalOwnerCount.owner = + adjustOwnerCountImpl(view, accountSle, sfOwnerCount, accountID, adjustment, j); + view.adjustOwnerCountHook(accountID, currentOwnerCount, totalOwnerCount); + } + else + { + XRPL_ASSERT(accountSle, "xrpl::adjustOwnerCountSigned : valid account sle"); + if (!accountSle) + return; + // the remaining are only asserts to preserve existing behavior + XRPL_ASSERT(sponsorSle == nullptr, "xrpl::adjustOwnerCountSigned : sponsor not enabled"); + XRPL_ASSERT( + accountSle->getType() == ltACCOUNT_ROOT, + "xrpl::adjustOwnerCountSigned : valid account sle type"); + XRPL_ASSERT(adjustment, "xrpl::adjustOwnerCount : nonzero adjustment input"); + std::uint32_t const current{accountSle->getFieldU32(sfOwnerCount)}; + AccountID const id = (*accountSle)[sfAccount]; + std::uint32_t const adjusted = confineOwnerCount(current, adjustment, id, j); + + OwnerCounts const currentOwnerCount(accountSle); + OwnerCounts finalOwnerCount(currentOwnerCount); + finalOwnerCount.owner = adjusted; + + view.adjustOwnerCountHook(id, currentOwnerCount, finalOwnerCount); + accountSle->at(sfOwnerCount) = adjusted; + view.update(accountSle); + } +} + +} // namespace + +std::uint32_t +ownerCount(SLE::const_ref sle, beast::Journal j, std::int32_t ownerCountAdj) +{ + XRPL_ASSERT(sle && sle->getType() == ltACCOUNT_ROOT, "xrpl::ownerCount : sle is account root"); + + AccountID const id = sle->getAccountID(sfAccount); + std::uint32_t const currentOwnerCount = sle->at(sfOwnerCount); + std::uint32_t const sponsoredOwnerCount = sle->at(sfSponsoredOwnerCount); + std::uint32_t const sponsoringOwnerCount = sle->at(sfSponsoringOwnerCount); + + XRPL_ASSERT( + currentOwnerCount >= sponsoredOwnerCount, + "xrpl::ownerCount : OwnerCount must be greater than or equal to SponsoredOwnerCount"); + + std::int64_t deltaCount = + static_cast(ownerCountAdj) - sponsoredOwnerCount + sponsoringOwnerCount; + + if (deltaCount > std::numeric_limits::max()) + { + // LCOV_EXCL_START + deltaCount = std::numeric_limits::max(); + JLOG(j.fatal()) << "Account " << id << " delta count exceeds max, " + << "adjustment: " << ownerCountAdj + << ", sponsoredCount: " << sponsoredOwnerCount + << ", sponsoringOwnerCount: " << sponsoringOwnerCount; + // LCOV_EXCL_STOP + } + else if (deltaCount < std::numeric_limits::min()) + { + // LCOV_EXCL_START + deltaCount = std::numeric_limits::min(); + JLOG(j.fatal()) << "Account " << id << " delta count is below min, " + << "adjustment: " << ownerCountAdj + << ", sponsoredCount: " << sponsoredOwnerCount + << ", sponsoringCount: " << sponsoringOwnerCount; + // LCOV_EXCL_STOP + } + + return confineOwnerCount(currentOwnerCount, deltaCount); } XRPAmount @@ -91,12 +274,14 @@ xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, return beast::kZero; // Return balance minus reserve - std::uint32_t const ownerCount = - confineOwnerCount(view.ownerCountHook(id, sle->getFieldU32(sfOwnerCount)), ownerCountAdj); + std::uint32_t const currentOwnerCount = + confineOwnerCount(view.ownerCountHook(id, OwnerCounts(sle)).count(), ownerCountAdj); + std::uint32_t const currentAccountCount = accountCountImpl(sle, 0, j); // Pseudo-accounts have no reserve requirement - auto const reserve = - isPseudoAccount(sle) ? XRPAmount{0} : view.fees().accountReserve(ownerCount); + auto const reserve = isPseudoAccount(sle) + ? XRPAmount{0} + : view.fees().accountReserve(currentOwnerCount, currentAccountCount); auto const fullBalance = sle->getFieldAmount(sfBalance); @@ -108,7 +293,7 @@ xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, << " amount=" << amount.getFullText() << " fullBalance=" << fullBalance.getFullText() << " balance=" << balance.getFullText() << " reserve=" << reserve - << " ownerCount=" << ownerCount << " ownerCountAdj=" << ownerCountAdj; + << " ownerCount=" << currentOwnerCount << " ownerCountAdj=" << ownerCountAdj; return amount.xrp(); } @@ -125,19 +310,193 @@ transferRate(ReadView const& view, AccountID const& issuer) } void -adjustOwnerCount(ApplyView& view, SLE::ref sle, std::int32_t amount, beast::Journal j) +increaseOwnerCount( + ApplyView& view, + SLE::ref accountSle, + SLE::ref sponsorSle, + std::uint32_t count, + beast::Journal j) { - if (!sle) - return; - XRPL_ASSERT(amount, "xrpl::adjustOwnerCount : nonzero amount input"); - std::uint32_t const current{sle->getFieldU32(sfOwnerCount)}; - AccountID const id = (*sle)[sfAccount]; - std::uint32_t const adjusted = confineOwnerCount(current, amount, id, j); - view.adjustOwnerCountHook(id, current, adjusted); - sle->at(sfOwnerCount) = adjusted; - view.update(sle); + XRPL_ASSERT( + count != 0 && count <= std::numeric_limits::max(), + "xrpl::increaseOwnerCount : count in signed delta range"); + if (count == 0 || count > std::numeric_limits::max()) + return; // LCOV_EXCL_LINE + + adjustOwnerCountSigned(view, accountSle, sponsorSle, static_cast(count), j); } +void +increaseOwnerCount(ApplyViewContext ctx, SLE::ref accountSle, std::uint32_t count, beast::Journal j) +{ + auto const sponsorExp = getEffectiveTxReserveSponsor(ctx, accountSle); + + // The sponsor's existence is validated by checkReserve/checkSponsor before + // any owner-count mutation, so loading it here cannot fail. + XRPL_ASSERT( + sponsorExp.has_value(), "xrpl::increaseOwnerCount : sponsor validated before mutation"); + + increaseOwnerCount(ctx.view, accountSle, sponsorExp ? *sponsorExp : SLE::pointer(), count, j); +} + +void +decreaseOwnerCount( + ApplyView& view, + SLE::ref accountSle, + SLE::ref sponsorSle, + std::uint32_t count, + beast::Journal j) +{ + XRPL_ASSERT( + count != 0 && count <= std::numeric_limits::max(), + "xrpl::decreaseOwnerCount : count in signed delta range"); + if (count == 0 || count > std::numeric_limits::max()) + return; // LCOV_EXCL_LINE + + adjustOwnerCountSigned(view, accountSle, sponsorSle, -static_cast(count), j); +} + +void +decreaseOwnerCountForObject( + ApplyView& view, + SLE::ref accountSle, + SLE::ref objectSle, + std::uint32_t count, + beast::Journal j) +{ + XRPL_ASSERT(objectSle, "xrpl::decreaseOwnerCountForObject : valid object sle"); + if (!objectSle) + return; // LCOV_EXCL_LINE + + bool const validObjectType = objectSle->getType() != ltACCOUNT_ROOT; + XRPL_ASSERT(validObjectType, "xrpl::decreaseOwnerCountForObject : valid object sle type"); + if (!validObjectType) + return; // LCOV_EXCL_LINE + + SLE::ref sponsorSle = getLedgerEntryReserveSponsor(view, objectSle); + decreaseOwnerCount(view, accountSle, sponsorSle, count, j); +} + +void +adjustLoanBrokerOwnerCount( + ApplyView& view, + SLE::ref brokerSle, + std::int32_t delta, + beast::Journal j) +{ + XRPL_ASSERT( + brokerSle && brokerSle->getType() == ltLOAN_BROKER, + "xrpl::adjustLoanBrokerOwnerCount : valid loan broker sle"); + if (!brokerSle || brokerSle->getType() != ltLOAN_BROKER) + return; // LCOV_EXCL_LINE + + XRPL_ASSERT(delta != 0, "xrpl::adjustLoanBrokerOwnerCount : nonzero delta input"); + if (delta == 0) + return; // LCOV_EXCL_LINE + + adjustOwnerCountImpl( + view, brokerSle, sfOwnerCount, brokerSle->getAccountID(sfAccount), delta, j); +} + +XRPAmount +accountReserve(ReadView const& view, SLE::const_ref sle, beast::Journal j, Adjustment adj) +{ + XRPL_ASSERT(sle && sle->getType() == ltACCOUNT_ROOT, "xrpl::accountReserve : valid sle"); + + if (!view.rules().enabled(featureSponsor)) + { + XRPL_ASSERT(adj.accountCountDelta == 0, "xrpl::accountReserve : no account count delta"); + return view.fees().accountReserve(sle->getFieldU32(sfOwnerCount) + adj.ownerCountDelta, 1); + } + std::uint32_t const currentOwnerCount = ownerCount(sle, j, adj.ownerCountDelta); + std::uint32_t const currentAccountCount = accountCountImpl(sle, adj.accountCountDelta, j); + + return view.fees().accountReserve(currentOwnerCount, currentAccountCount); +} + +TER +checkReserve( + ApplyViewContext ctx, + SLE::const_ref accSle, + XRPAmount accBalance, + SLE::const_ref sponsorSle, + Adjustment adj, + beast::Journal j, + TER insufReserveCode) +{ + // TODO: swap to assert after fixCleanup3_2_0 is retired + if (!accSle || accSle->getType() != ltACCOUNT_ROOT) + return tefINTERNAL; // LCOV_EXCL_LINE + XRPL_ASSERT( + !isTesSuccess(insufReserveCode), "xrpl::checkReserve : insufReserveCode is not tesSUCCESS"); + if (ctx.view.rules().enabled(featureSponsor)) + { + if (sponsorSle) + { + if (sponsorSle->getType() != ltACCOUNT_ROOT) + return tefINTERNAL; // LCOV_EXCL_LINE + + auto const sle = ctx.view.read( + keylet::sponsorship( + sponsorSle->getAccountID(sfAccount), accSle->getAccountID(sfAccount))); + + // A reserve-sponsored tx must carry a sponsor signature + // (cosigning path) and/or have a pre-existing sponsorship SLE + // (prefunded path). Absence of both is an internal invariant break. + if (isReserveSponsored(ctx.tx) && !sle && !ctx.tx.isFieldPresent(sfSponsorSignature)) + return tecINTERNAL; // LCOV_EXCL_LINE + + if (sle) + { + auto const ownerCountAllowed = sle->getFieldU32(sfRemainingOwnerCount); + if (adj.ownerCountDelta > 0 && + ownerCountAllowed < static_cast(adj.ownerCountDelta)) + return insufReserveCode; + } + + auto const sponsorBalance = sponsorSle->getFieldAmount(sfBalance).xrp(); + XRPAmount const sponsorReserve = accountReserve(ctx.view, sponsorSle, j, adj); + + if (sponsorBalance < sponsorReserve) + return insufReserveCode; + } + else + { + XRPAmount const reserve = accountReserve(ctx.view, accSle, j, adj); + if (accBalance < reserve) + return insufReserveCode; + } + } + else + { + XRPL_ASSERT( + !sponsorSle, + "xrpl::checkReserve : featureSponsor disabled and sponsorSle not provided"); + XRPL_ASSERT(adj.accountCountDelta == 0, "xrpl::checkReserve : accountCountDelta is 0"); + auto const reserve = ctx.view.fees().accountReserve( + accSle->getFieldU32(sfOwnerCount) + adj.ownerCountDelta, 1); + if (accBalance < reserve) + return insufReserveCode; + } + return tesSUCCESS; +} + +TER +checkReserve( + ApplyViewContext ctx, + SLE::const_ref accSle, + XRPAmount accBalance, + Adjustment adj, + beast::Journal j) +{ + auto const sponsorExp = getEffectiveTxReserveSponsor(ctx, accSle); + if (!sponsorExp) + return sponsorExp.error(); // LCOV_EXCL_LINE + return checkReserve(ctx, accSle, accBalance, *sponsorExp, adj, j); +} + +// ---------------------------------------------------- + AccountID pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey) { diff --git a/src/libxrpl/ledger/helpers/CredentialHelpers.cpp b/src/libxrpl/ledger/helpers/CredentialHelpers.cpp index 6fc2faf03e..226ea100e9 100644 --- a/src/libxrpl/ledger/helpers/CredentialHelpers.cpp +++ b/src/libxrpl/ledger/helpers/CredentialHelpers.cpp @@ -97,7 +97,7 @@ deleteSLE(ApplyView& view, SLE::ref sleCredential, beast::Journal j) } if (isOwner) - adjustOwnerCount(view, sleAccount, -1, j); + decreaseOwnerCountForObject(view, sleAccount, sleCredential, 1, j); return tesSUCCESS; }; diff --git a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp index 3c4e55a16e..ac14c3b66d 100644 --- a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -11,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -125,29 +125,29 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue) [[nodiscard]] TER addEmptyHolding( - ApplyView& view, + ApplyViewContext ctx, AccountID const& accountID, XRPAmount priorBalance, MPTIssue const& mptIssue, beast::Journal journal) { auto const& mptID = mptIssue.getMptID(); - auto const mpt = view.peek(keylet::mptokenIssuance(mptID)); + auto const mpt = ctx.view.peek(keylet::mptokenIssuance(mptID)); if (!mpt) return tefINTERNAL; // LCOV_EXCL_LINE if (mpt->isFlag(lsfMPTLocked)) return tefINTERNAL; // LCOV_EXCL_LINE - if (view.peek(keylet::mptoken(mptID, accountID))) + if (ctx.view.peek(keylet::mptoken(mptID, accountID))) return tecDUPLICATE; if (accountID == mptIssue.getIssuer()) return tesSUCCESS; - return authorizeMPToken(view, priorBalance, mptID, accountID, journal); + return authorizeMPToken(ctx, priorBalance, mptID, accountID, journal); } [[nodiscard]] TER authorizeMPToken( - ApplyView& view, + ApplyViewContext ctx, XRPAmount const& priorBalance, MPTID const& mptIssuanceID, AccountID const& account, @@ -155,7 +155,7 @@ authorizeMPToken( std::uint32_t flags, std::optional holderID) { - auto const sleAcct = view.peek(keylet::account(account)); + auto const sleAcct = ctx.view.peek(keylet::account(account)); if (!sleAcct) return tecINTERNAL; // LCOV_EXCL_LINE @@ -170,19 +170,19 @@ authorizeMPToken( if ((flags & tfMPTUnauthorize) != 0u) { auto const mptokenKey = keylet::mptoken(mptIssuanceID, account); - auto const sleMpt = view.peek(mptokenKey); + auto const sleMpt = ctx.view.peek(mptokenKey); if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0 || - (view.rules().enabled(fixCleanup3_1_3) && + (ctx.view.rules().enabled(fixCleanup3_1_3) && (*sleMpt)[~sfLockedAmount].valueOr(0) != 0)) return tecINTERNAL; // LCOV_EXCL_LINE - if (!view.dirRemove( + if (!ctx.view.dirRemove( keylet::ownerDir(account), (*sleMpt)[sfOwnerNode], sleMpt->key(), false)) return tecINTERNAL; // LCOV_EXCL_LINE - adjustOwnerCount(view, sleAcct, -1, journal); + decreaseOwnerCountForObject(ctx.view, sleAcct, sleMpt, 1, journal); - view.erase(sleMpt); + ctx.view.erase(sleMpt); return tesSUCCESS; } @@ -190,47 +190,57 @@ authorizeMPToken( // - add the new mptokenKey to the owner directory // - create the MPToken object for the holder + // A reserve sponsor only covers tx.Account's own objects. + auto const sponsorExp = getEffectiveTxReserveSponsor(ctx, sleAcct); + if (!sponsorExp) + return sponsorExp.error(); // LCOV_EXCL_LINE + auto const sponsorSle = *sponsorExp; + // The reserve that is required to create the MPToken. Note // that although the reserve increases with every item // an account owns, in the case of MPTokens we only // *enforce* a reserve if the user owns more than two // items. This is similar to the reserve requirements of trust lines. - std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount); - XRPAmount const reserveCreate( - (uOwnerCount < 2) ? XRPAmount(beast::kZero) - : view.fees().accountReserve(uOwnerCount + 1)); - - if (priorBalance < reserveCreate) - return tecINSUFFICIENT_RESERVE; + // The "free-tier" shortcut (ownerCount < 2) does not apply once a sponsor is on + // the tx — the sponsor must always cover the reserve (via balance or prefunded + // budget), so this check always runs for sponsored transactions. + if (sponsorSle || ownerCount(sleAcct, journal) >= 2) + { + if (auto const ret = checkReserve( + ctx, sleAcct, priorBalance, sponsorSle, {.ownerCountDelta = 1}, journal); + !isTesSuccess(ret)) + return ret; + } // Defensive check before we attempt to create MPToken for the issuer - auto const mpt = view.read(keylet::mptokenIssuance(mptIssuanceID)); + auto const mpt = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID)); if (!mpt || mpt->getAccountID(sfIssuer) == account) { // LCOV_EXCL_START UNREACHABLE("xrpl::authorizeMPToken : invalid issuance or issuers token"); - if (view.rules().enabled(featureLendingProtocol)) + if (ctx.view.rules().enabled(featureLendingProtocol)) return tecINTERNAL; // LCOV_EXCL_STOP } auto const mptokenKey = keylet::mptoken(mptIssuanceID, account); auto mptoken = std::make_shared(mptokenKey); - if (auto ter = dirLink(view, account, mptoken)) + if (auto ter = dirLink(ctx.view, account, mptoken)) return ter; // LCOV_EXCL_LINE (*mptoken)[sfAccount] = account; (*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID; (*mptoken)[sfFlags] = 0; - view.insert(mptoken); + ctx.view.insert(mptoken); // Update owner count. - adjustOwnerCount(view, sleAcct, 1, journal); + increaseOwnerCount(ctx.view, sleAcct, sponsorSle, 1, journal); + addSponsorToLedgerEntry(mptoken, sponsorSle); return tesSUCCESS; } - auto const sleMptIssuance = view.read(keylet::mptokenIssuance(mptIssuanceID)); + auto const sleMptIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID)); if (!sleMptIssuance) return tecINTERNAL; // LCOV_EXCL_LINE @@ -240,7 +250,7 @@ authorizeMPToken( if (account != (*sleMptIssuance)[sfIssuer]) return tecINTERNAL; // LCOV_EXCL_LINE - auto const sleMpt = view.peek(keylet::mptoken(mptIssuanceID, *holderID)); + auto const sleMpt = ctx.view.peek(keylet::mptoken(mptIssuanceID, *holderID)); if (!sleMpt) return tecINTERNAL; // LCOV_EXCL_LINE @@ -263,13 +273,13 @@ authorizeMPToken( if (flagsIn != flagsOut) sleMpt->setFieldU32(sfFlags, flagsOut); - view.update(sleMpt); + ctx.view.update(sleMpt); return tesSUCCESS; } [[nodiscard]] TER removeEmptyHolding( - ApplyView& view, + ApplyViewContext ctx, AccountID const& accountID, MPTIssue const& mptIssue, beast::Journal journal) @@ -279,7 +289,7 @@ removeEmptyHolding( // a token does exist, it will get deleted. If not, return success. bool const accountIsIssuer = accountID == mptIssue.getIssuer(); auto const& mptID = mptIssue.getMptID(); - auto const mptoken = view.peek(keylet::mptoken(mptID, accountID)); + auto const mptoken = ctx.view.peek(keylet::mptoken(mptID, accountID)); if (!mptoken) return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND; // Unlike a trust line, if the account is the issuer, and the token has a @@ -287,7 +297,7 @@ removeEmptyHolding( // accounting out of balance, so fail. Since this should be impossible // anyway, I'm not going to put any effort into it. if (mptoken->at(sfMPTAmount) != 0 || - (view.rules().enabled(fixCleanup3_1_3) && (*mptoken)[~sfLockedAmount].valueOr(0) != 0)) + (ctx.view.rules().enabled(fixCleanup3_1_3) && (*mptoken)[~sfLockedAmount].valueOr(0) != 0)) return tecHAS_OBLIGATIONS; // Don't delete if the token still has confidential balances @@ -300,7 +310,7 @@ removeEmptyHolding( } return authorizeMPToken( - view, + ctx, {}, // priorBalance mptID, accountID, @@ -419,13 +429,13 @@ requireAuth( [[nodiscard]] TER enforceMPTokenAuthorization( - ApplyView& view, + ApplyViewContext ctx, MPTID const& mptIssuanceID, AccountID const& account, XRPAmount const& priorBalance, // for MPToken authorization beast::Journal j) { - auto const sleIssuance = view.read(keylet::mptokenIssuance(mptIssuanceID)); + auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID)); if (!sleIssuance) return tefINTERNAL; // LCOV_EXCL_LINE @@ -437,7 +447,7 @@ enforceMPTokenAuthorization( return tefINTERNAL; // LCOV_EXCL_LINE auto const keylet = keylet::mptoken(mptIssuanceID, account); - auto const sleToken = view.read(keylet); // NOTE: might be null + auto const sleToken = ctx.view.read(keylet); // NOTE: might be null auto const maybeDomainID = sleIssuance->at(~sfDomainID); bool expired = false; bool const authorizedByDomain = [&]() -> bool { @@ -445,7 +455,7 @@ enforceMPTokenAuthorization( if (!maybeDomainID.has_value()) return false; // LCOV_EXCL_LINE - auto const ter = verifyValidDomain(view, account, *maybeDomainID, j); + auto const ter = verifyValidDomain(ctx.view, account, *maybeDomainID, j); if (isTesSuccess(ter)) return true; if (ter == tecEXPIRED) @@ -500,7 +510,7 @@ enforceMPTokenAuthorization( maybeDomainID.has_value() && sleToken == nullptr, "xrpl::enforceMPTokenAuthorization : new MPToken for domain"); if (auto const err = authorizeMPToken( - view, + ctx, priorBalance, // priorBalance mptIssuanceID, // mptIssuanceID account, // account @@ -915,6 +925,7 @@ createMPToken( ApplyView& view, MPTID const& mptIssuanceID, AccountID const& account, + SLE::ref sponsorSle, std::uint32_t const flags) { auto const mptokenKey = keylet::mptoken(mptIssuanceID, account); @@ -931,6 +942,8 @@ createMPToken( (*mptoken)[sfFlags] = flags; (*mptoken)[sfOwnerNode] = *ownerNode; + addSponsorToLedgerEntry(mptoken, sponsorSle); + view.insert(mptoken); return tesSUCCESS; @@ -941,6 +954,7 @@ checkCreateMPT( xrpl::ApplyView& view, xrpl::MPTIssue const& mptIssue, xrpl::AccountID const& holder, + SLE::ref sponsorSle, beast::Journal j) { if (mptIssue.getIssuer() == holder) @@ -950,7 +964,7 @@ checkCreateMPT( auto const mptokenID = keylet::mptoken(mptIssuanceID.key, holder); if (!view.exists(mptokenID)) { - if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, 0); + if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, sponsorSle, 0); !isTesSuccess(err)) { return err; @@ -960,7 +974,8 @@ checkCreateMPT( { return tecINTERNAL; } - adjustOwnerCount(view, sleAcct, 1, j); + + increaseOwnerCount(view, sleAcct, sponsorSle, 1, j); } return tesSUCCESS; } diff --git a/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp b/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp index 6dca715a8c..589e49d335 100644 --- a/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp @@ -258,7 +258,9 @@ changeTokenURI( return tesSUCCESS; } -/** Insert the token in the owner's token directory. */ +/** + * Insert the token in the owner's token directory. + */ TER insertToken(ApplyView& view, AccountID owner, STObject&& nft) { @@ -269,11 +271,7 @@ insertToken(ApplyView& view, AccountID owner, STObject&& nft) // the NFT. SLE::pointer const page = getPageForToken(view, owner, nft[sfNFTokenID], [](ApplyView& view, AccountID const& owner) { - adjustOwnerCount( - view, - view.peek(keylet::account(owner)), - 1, - beast::Journal{beast::Journal::getNullSink()}); + increaseOwnerCount(view, owner, {}, 1, beast::Journal{beast::Journal::getNullSink()}); }); if (!page) @@ -351,7 +349,9 @@ mergePages(ApplyView& view, SLE::ref p1, SLE::ref p2) return true; } -/** Remove the token from the owner's token directory. */ +/** + * Remove the token from the owner's token directory. + */ TER removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID) { @@ -364,7 +364,9 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID) return removeToken(view, owner, nftokenID, page); } -/** Remove the token from the owner's token directory. */ +/** + * Remove the token from the owner's token directory. + */ TER removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, SLE::ref curr) { @@ -411,21 +413,17 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S curr->setFieldArray(sfNFTokens, arr); view.update(curr); - int cnt = 0; + std::uint32_t cnt = 0; if (prev && mergePages(view, prev, curr)) - cnt--; + ++cnt; if (next && mergePages(view, curr, next)) - cnt--; + ++cnt; if (cnt != 0) { - adjustOwnerCount( - view, - view.peek(keylet::account(owner)), - cnt, - beast::Journal{beast::Journal::getNullSink()}); + decreaseOwnerCount(view, owner, {}, cnt, beast::Journal{beast::Journal::getNullSink()}); } return tesSUCCESS; @@ -460,11 +458,7 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S curr->makeFieldAbsent(sfPreviousPageMin); } - adjustOwnerCount( - view, - view.peek(keylet::account(owner)), - -1, - beast::Journal{beast::Journal::getNullSink()}); + decreaseOwnerCount(view, owner, {}, 1, beast::Journal{beast::Journal::getNullSink()}); view.update(curr); view.erase(prev); @@ -502,7 +496,7 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S view.erase(curr); - int cnt = 1; + uint32_t cnt = 1; // Since we're here, try to consolidate the previous and current pages // of the page we removed (if any) into one. mergePages() _should_ @@ -519,11 +513,7 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S view.peek(Keylet(ltNFTOKEN_PAGE, next->key())))) cnt++; - adjustOwnerCount( - view, - view.peek(keylet::account(owner)), - -1 * cnt, - beast::Journal{beast::Journal::getNullSink()}); + decreaseOwnerCount(view, owner, {}, cnt, beast::Journal{beast::Journal::getNullSink()}); return tesSUCCESS; } @@ -639,8 +629,7 @@ deleteTokenOffer(ApplyView& view, SLE::ref offer) false)) return false; - adjustOwnerCount( - view, view.peek(keylet::account(owner)), -1, beast::Journal{beast::Journal::getNullSink()}); + decreaseOwnerCount(view, owner, {}, 1, beast::Journal{beast::Journal::getNullSink()}); view.erase(offer); return true; @@ -743,9 +732,11 @@ repairNFTokenDirectoryLinks(ApplyView& view, AccountID const& owner) auto const newPrev = view.peek(Keylet(ltNFTOKEN_PAGE, *prevLink)); if (!newPrev) { + // LCOV_EXCL_START Throw( "NFTokenPage directory for " + to_string(owner) + - " cannot be repaired. Unexpected link problem."); + " cannot be repaired. Unexpected link problem."); + // LCOV_EXCL_STOP } newPrev->at(sfNextPageMin) = nextPage->key(); view.update(newPrev); @@ -931,7 +922,7 @@ tokenOfferCreateApply( { Keylet const acctKeylet = keylet::account(acctID); if (auto const acct = view.read(acctKeylet); - priorBalance < view.fees().accountReserve((*acct)[sfOwnerCount] + 1)) + priorBalance < accountReserve(view, acct, j, {.ownerCountDelta = 1})) return tecINSUFFICIENT_RESERVE; auto const offerID = keylet::nftokenOffer(acctID, seqProxy.value()); @@ -983,7 +974,7 @@ tokenOfferCreateApply( } // Update owner count. - adjustOwnerCount(view, view.peek(acctKeylet), 1, j); + increaseOwnerCount(view, acctID, {}, 1, j); return tesSUCCESS; } diff --git a/src/libxrpl/ledger/helpers/OfferHelpers.cpp b/src/libxrpl/ledger/helpers/OfferHelpers.cpp index 6e72b71564..caa6ffb8ae 100644 --- a/src/libxrpl/ledger/helpers/OfferHelpers.cpp +++ b/src/libxrpl/ledger/helpers/OfferHelpers.cpp @@ -55,7 +55,7 @@ offerDelete(ApplyView& view, SLE::ref sle, beast::Journal j) } } - adjustOwnerCount(view, view.peek(keylet::account(owner)), -1, j); + decreaseOwnerCountForObject(view, owner, sle, 1, j); view.erase(sle); diff --git a/src/libxrpl/ledger/helpers/PaymentChannelHelpers.cpp b/src/libxrpl/ledger/helpers/PaymentChannelHelpers.cpp index 03b68e8860..d5a0b25681 100644 --- a/src/libxrpl/ledger/helpers/PaymentChannelHelpers.cpp +++ b/src/libxrpl/ledger/helpers/PaymentChannelHelpers.cpp @@ -58,7 +58,7 @@ closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal XRPL_ASSERT( (*slep)[sfAmount] >= (*slep)[sfBalance], "xrpl::closeChannel : minimum channel amount"); (*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance]; - adjustOwnerCount(view, sle, -1, j); + decreaseOwnerCountForObject(view, sle, slep, 1, j); view.update(sle); // Remove PayChan from ledger diff --git a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp index 5a2995c030..868c9fb26d 100644 --- a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp +++ b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -195,6 +196,7 @@ trustCreate( // Issuer should be the account being set. std::uint32_t uQualityIn, std::uint32_t uQualityOut, + SLE::ref sponsorSle, beast::Journal j) { JLOG(j.trace()) << "trustCreate: " << to_string(uSrcAccountID) << ", " @@ -281,7 +283,9 @@ trustCreate( } sleRippleState->setFieldU32(sfFlags, uFlags); - adjustOwnerCount(view, sleAccount, 1, j); + increaseOwnerCount(view, sleAccount, sponsorSle, 1, j); + + addSponsorToLedgerEntry(sleRippleState, sponsorSle, bSetHigh ? sfHighSponsor : sfLowSponsor); // ONLY: Create ripple balance. sleRippleState->setFieldAmount(sfBalance, bSetHigh ? -saBalance : saBalance); @@ -317,6 +321,9 @@ trustDelete( return tefBAD_LEDGER; // LCOV_EXCL_LINE } + removeSponsorFromLedgerEntry(sleRippleState, sfHighSponsor); + removeSponsorFromLedgerEntry(sleRippleState, sfLowSponsor); + JLOG(j.trace()) << "trustDelete: Deleting ripple line: state"; view.erase(sleRippleState); @@ -369,11 +376,15 @@ updateTrustLine( { // VFALCO Where is the line being deleted? // Clear the reserve of the sender, possibly delete the line! - adjustOwnerCount(view, sle, -1, j); + auto const currentSponsor = + getLedgerEntryReserveSponsor(view, state, bSenderHigh ? sfHighSponsor : sfLowSponsor); + decreaseOwnerCount(view, sle, currentSponsor, 1, j); // Clear reserve flag. state->clearFlag(senderReserveFlag); + removeSponsorFromLedgerEntry(state, !bSenderHigh ? sfLowSponsor : sfHighSponsor); + // Balance is zero, receiver reserve is clear. if (!after && !state->isFlag(receiverReserveFlag)) return true; @@ -381,12 +392,14 @@ updateTrustLine( return false; } +// Only used in tests TER issueIOU( ApplyView& view, AccountID const& account, STAmount const& amount, Issue const& issue, + SLE::ref sponsorSle, beast::Journal j) { XRPL_ASSERT( @@ -472,6 +485,7 @@ issueIOU( limit, 0, 0, + sponsorSle, j); } @@ -620,7 +634,7 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc TER addEmptyHolding( - ApplyView& view, + ApplyViewContext ctx, AccountID const& accountID, XRPAmount priorBalance, Issue const& issue, @@ -632,30 +646,45 @@ addEmptyHolding( auto const& issuerId = issue.getIssuer(); auto const& currency = issue.currency; - if (isGlobalFrozen(view, issuerId)) + if (isGlobalFrozen(ctx.view, issuerId)) return tecFROZEN; // LCOV_EXCL_LINE auto const& srcId = issuerId; auto const& dstId = accountID; auto const high = srcId > dstId; auto const index = keylet::trustLine(srcId, dstId, currency); - auto const sleSrc = view.peek(keylet::account(srcId)); - auto const sleDst = view.peek(keylet::account(dstId)); + auto const sleSrc = ctx.view.peek(keylet::account(srcId)); + auto const sleDst = ctx.view.peek(keylet::account(dstId)); if (!sleDst || !sleSrc) return tefINTERNAL; // LCOV_EXCL_LINE if (!sleSrc->isFlag(lsfDefaultRipple)) return tecINTERNAL; // LCOV_EXCL_LINE // If the line already exists, don't create it again. - if (view.read(index)) + if (ctx.view.read(index)) return tecDUPLICATE; + // A reserve sponsor only covers tx.Account's own objects. + auto const sponsorExp = getEffectiveTxReserveSponsor(ctx, sleDst); + if (!sponsorExp) + return sponsorExp.error(); // LCOV_EXCL_LINE + auto const sponsorSle = *sponsorExp; + // Can the account cover the trust line reserve ? - std::uint32_t const ownerCount = sleDst->at(sfOwnerCount); - if (priorBalance < view.fees().accountReserve(ownerCount + 1)) - return tecNO_LINE_INSUF_RESERVE; + if (auto const ret = checkReserve( + ctx, + sleDst, + priorBalance, + sponsorSle, + {.ownerCountDelta = 1}, + journal, + tecNO_LINE_INSUF_RESERVE); + !isTesSuccess(ret)) + { + return ret; + } return trustCreate( - view, + ctx.view, high, srcId, dstId, @@ -669,19 +698,20 @@ addEmptyHolding( /*saLimit=*/STAmount{Issue{currency, dstId}}, /*uQualityIn=*/0, /*uQualityOut=*/0, + sponsorSle, journal); } TER removeEmptyHolding( - ApplyView& view, + ApplyViewContext ctx, AccountID const& accountID, Issue const& issue, beast::Journal journal) { if (issue.native()) { - auto const sle = view.read(keylet::account(accountID)); + auto const sle = ctx.view.read(keylet::account(accountID)); if (!sle) return tecINTERNAL; // LCOV_EXCL_LINE @@ -696,7 +726,7 @@ removeEmptyHolding( // If the account is the issuer, then no line should exist. Check anyway. // If a line does exist, it will get deleted. If not, return success. bool const accountIsIssuer = accountID == issue.account; - auto const line = view.peek(keylet::trustLine(accountID, issue)); + auto const line = ctx.view.peek(keylet::trustLine(accountID, issue)); if (!line) return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND; if (!accountIsIssuer && line->at(sfBalance)->iou() != beast::kZero) @@ -706,33 +736,43 @@ removeEmptyHolding( if (line->isFlag(lsfLowReserve)) { // Clear reserve for low account. - auto sleLowAccount = view.peek(keylet::account(line->at(sfLowLimit)->getIssuer())); + auto sleLowAccount = ctx.view.peek(keylet::account(line->at(sfLowLimit)->getIssuer())); if (!sleLowAccount) return tecINTERNAL; // LCOV_EXCL_LINE - adjustOwnerCount(view, sleLowAccount, -1, journal); + auto const currentLowSponsor = getLedgerEntryReserveSponsor(ctx.view, line, sfLowSponsor); + + decreaseOwnerCount(ctx.view, sleLowAccount, currentLowSponsor, 1, journal); // It's not really necessary to clear the reserve flag, since the line // is about to be deleted, but this will make the metadata reflect an // accurate state at the time of deletion. line->clearFlag(lsfLowReserve); + removeSponsorFromLedgerEntry(line, sfLowSponsor); } if (line->isFlag(lsfHighReserve)) { // Clear reserve for high account. - auto sleHighAccount = view.peek(keylet::account(line->at(sfHighLimit)->getIssuer())); + auto sleHighAccount = ctx.view.peek(keylet::account(line->at(sfHighLimit)->getIssuer())); if (!sleHighAccount) return tecINTERNAL; // LCOV_EXCL_LINE - adjustOwnerCount(view, sleHighAccount, -1, journal); + auto const currentHighSponsor = getLedgerEntryReserveSponsor(ctx.view, line, sfHighSponsor); + + decreaseOwnerCount(ctx.view, sleHighAccount, currentHighSponsor, 1, journal); // It's not really necessary to clear the reserve flag, since the line // is about to be deleted, but this will make the metadata reflect an // accurate state at the time of deletion. line->clearFlag(lsfHighReserve); + removeSponsorFromLedgerEntry(line, sfHighSponsor); } return trustDelete( - view, line, line->at(sfLowLimit)->getIssuer(), line->at(sfHighLimit)->getIssuer(), journal); + ctx.view, + line, + line->at(sfLowLimit)->getIssuer(), + line->at(sfHighLimit)->getIssuer(), + journal); } TER @@ -768,6 +808,9 @@ deleteAMMTrustLine( if (ammAccountID && (low != *ammAccountID && high != *ammAccountID)) return terNO_AMM; + auto const sponsorSle = + getLedgerEntryReserveSponsor(view, sleState, !ammLow ? sfLowSponsor : sfHighSponsor); + if (auto const ter = trustDelete(view, sleState, low, high, j); !isTesSuccess(ter)) { JLOG(j.error()) << "deleteAMMTrustLine: failed to delete the trustline."; @@ -778,7 +821,7 @@ deleteAMMTrustLine( if (!sleState->isFlag(uFlags)) return tecINTERNAL; // LCOV_EXCL_LINE - adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, -1, j); + decreaseOwnerCount(view, !ammLow ? sleLow : sleHigh, sponsorSle, 1, j); return tesSUCCESS; } diff --git a/src/libxrpl/ledger/helpers/SponsorHelpers.cpp b/src/libxrpl/ledger/helpers/SponsorHelpers.cpp new file mode 100644 index 0000000000..7e0c041854 --- /dev/null +++ b/src/libxrpl/ledger/helpers/SponsorHelpers.cpp @@ -0,0 +1,345 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace xrpl { + +bool +isReserveSponsorAllowed(TxType txType) +{ + // Transaction types explicitly allow-listed for reserve sponsorship, for + // v1. Lazily-initialized function-local static: constructed once on first + // use, with no startup cost paid by clients that never call this. + static std::unordered_set const kReserveSponsorAllowed = { + ttDELEGATE_SET, + ttDEPOSIT_PREAUTH, + ttPAYMENT, + ttSIGNER_LIST_SET, + ttCHECK_CANCEL, + ttCHECK_CASH, + ttCHECK_CREATE, + ttESCROW_CANCEL, + ttESCROW_CREATE, + ttESCROW_FINISH, + ttPAYCHAN_CLAIM, + ttPAYCHAN_CREATE, + ttPAYCHAN_FUND, + ttCLAWBACK, + ttMPTOKEN_AUTHORIZE, + ttMPTOKEN_ISSUANCE_CREATE, + ttMPTOKEN_ISSUANCE_DESTROY, + ttMPTOKEN_ISSUANCE_SET, + ttTRUST_SET, + ttCREDENTIAL_ACCEPT, + ttCREDENTIAL_CREATE, + ttCREDENTIAL_DELETE, + ttACCOUNT_SET, + ttREGULAR_KEY_SET, + ttSPONSORSHIP_TRANSFER, + }; + return kReserveSponsorAllowed.contains(txType); +} + +std::optional +getTxReserveSponsorID(STTx const& tx) +{ + if (tx.isFieldPresent(sfSponsor) && isReserveSponsored(tx)) + { + XRPL_ASSERT( + getCurrentTransactionRules()->enabled( // NOLINT(bugprone-unchecked-optional-access) + featureSponsor), + "xrpl::getTxReserveSponsorID : sponsor exists + Sponsor enabled"); + return tx.getAccountID(sfSponsor); + } + return {}; +} + +std::expected +getTxReserveSponsor(ApplyViewContext ctx) +{ + auto const sponsorID = getTxReserveSponsorID(ctx.tx); + if (sponsorID) + { + XRPL_ASSERT( + ctx.view.rules().enabled(featureSponsor), + "xrpl::getTxReserveSponsor : sponsor exists + Sponsor enabled"); + auto sle = ctx.view.peek(keylet::account(*sponsorID)); + + // already checked in Transactor::checkSponsor + if (!sle) + return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE + return sle; + } + return SLE::pointer(); +} + +std::expected +getTxReserveSponsor(ReadView const& view, STTx const& tx) +{ + auto const sponsorID = getTxReserveSponsorID(tx); + if (sponsorID) + { + XRPL_ASSERT( + view.rules().enabled(featureSponsor), + "xrpl::getTxReserveSponsor : sponsor exists + Sponsor enabled"); + auto sle = view.read(keylet::account(*sponsorID)); + + // already checked in Transactor::checkSponsor + if (!sle) + return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE + return sle; + } + return SLE::pointer(); +} + +std::expected +getEffectiveTxReserveSponsor(ApplyViewContext ctx, SLE::const_ref accountSle) +{ + // A reserve sponsor only covers tx.Account's own objects. + if (ctx.view.rules().enabled(fixCleanup3_2_0)) + { + XRPL_ASSERT( + accountSle && accountSle->getType() == ltACCOUNT_ROOT, + "xrpl::getEffectiveTxReserveSponsor : accountSle exists and is account type"); + } + else + { + XRPL_ASSERT( + accountSle && + ((accountSle->getType() == ltACCOUNT_ROOT) || (accountSle->getType() == ltESCROW)), + "xrpl::getEffectiveTxReserveSponsor : accountSle exists and is account type"); + } + + if (isPseudoAccount(accountSle) || accountSle->getAccountID(sfAccount) != ctx.tx[sfAccount]) + return SLE::pointer(); + return getTxReserveSponsor(ctx); +} + +std::optional +getLedgerEntryReserveSponsorID(SLE::const_ref sle, SF_ACCOUNT const& field) +{ + XRPL_ASSERT( + (sle && + ((sle->getType() == ltRIPPLE_STATE && (field == sfHighSponsor || field == sfLowSponsor)) || + (sle->getType() != ltRIPPLE_STATE && field == sfSponsor))), + "xrpl::getLedgerEntryReserveSponsorID : correct sfield"); + + if (sle->isFieldPresent(field)) + return sle->getAccountID(field); + return {}; +} + +SLE::pointer +getLedgerEntryReserveSponsor(ApplyView& view, SLE::const_ref sle, SF_ACCOUNT const& field) +{ + auto const sponsorID = getLedgerEntryReserveSponsorID(sle, field); + if (sponsorID) + { + XRPL_ASSERT( + view.rules().enabled(featureSponsor), + "xrpl::getLedgerEntryReserveSponsor : sponsor exists + Sponsor enabled"); + return view.peek(keylet::account(*sponsorID)); + } + return {}; +} + +void +addSponsorToLedgerEntry(SLE::ref sle, SLE::const_ref sponsorSle, SF_ACCOUNT const& field) +{ + XRPL_ASSERT( + (sle->getType() == ltRIPPLE_STATE && (field == sfHighSponsor || field == sfLowSponsor)) || + (sle->getType() != ltRIPPLE_STATE && field == sfSponsor), + "addSponsorToLedgerEntry : Invalid field to the LedgerEntry"); + if (sponsorSle) + { + XRPL_ASSERT( + getCurrentTransactionRules()->enabled( // NOLINT(bugprone-unchecked-optional-access) + featureSponsor), + "xrpl::addSponsorToLedgerEntry : sponsor exists + Sponsor enabled"); + sle->setAccountID(field, sponsorSle->getAccountID(sfAccount)); + } +} + +void +addSponsorToLedgerEntry(ApplyViewContext ctx, SLE::ref sle, SF_ACCOUNT const& field) +{ + // getTxReserveSponsor yields a null pointer when the tx is not + // reserve-sponsored, so addSponsorToLedgerEntry becomes a no-op then. The + // error case (tecINTERNAL) is an already-checked invariant; skip stamping. + auto const sponsorSle = getTxReserveSponsor(ctx); + if (sponsorSle && *sponsorSle) + { + XRPL_ASSERT( + ctx.view.rules().enabled(featureSponsor), + "xrpl::addSponsorToLedgerEntry : sponsor exists + Sponsor enabled"); + addSponsorToLedgerEntry(sle, *sponsorSle, field); + } +} + +void +removeSponsorFromLedgerEntry(SLE::ref sle, SF_ACCOUNT const& field) +{ + XRPL_ASSERT( + (sle->getType() == ltRIPPLE_STATE && (field == sfHighSponsor || field == sfLowSponsor)) || + (sle->getType() != ltRIPPLE_STATE && field == sfSponsor), + "removeSponsorFromLedgerEntry : Invalid field to the LedgerEntry"); + if (sle->isFieldPresent(field)) + { + XRPL_ASSERT( + getCurrentTransactionRules()->enabled( // NOLINT(bugprone-unchecked-optional-access) + featureSponsor), + "xrpl::removeSponsorFromLedgerEntry : sponsor exists + Sponsor enabled"); + sle->makeFieldAbsent(field); + } +} + +bool +isLedgerEntryOwner(ReadView const& view, SLE const& sle, AccountID const& account) +{ + switch (sle.getType()) + { + case ltCHECK: + case ltESCROW: + case ltPAYCHAN: + case ltMPTOKEN: + case ltDELEGATE: + case ltDEPOSIT_PREAUTH: + return sle.getAccountID(sfAccount) == account; + case ltMPTOKEN_ISSUANCE: + return sle.getAccountID(sfIssuer) == account; + case ltSIGNER_LIST: { + auto const signerList = view.read(keylet::signerList(account)); + if (!signerList) + return false; + return signerList->key() == sle.key(); + } + case ltCREDENTIAL: { + auto const& ownerField = sle.isFlag(lsfAccepted) ? sfSubject : sfIssuer; + return sle.getAccountID(ownerField) == account; + } + case ltRIPPLE_STATE: { + if (sle.isFlag(lsfHighReserve)) + { + auto const highAccount = sle.getFieldAmount(sfHighLimit).getIssuer(); + if (highAccount == account) + return true; + } + if (sle.isFlag(lsfLowReserve)) + { + auto const lowAccount = sle.getFieldAmount(sfLowLimit).getIssuer(); + if (lowAccount == account) + return true; + } + // Reachable: the sponsee may be a third party or the side of the + // line that holds no reserve (e.g. the issuer). Callers map this + // to tecNO_PERMISSION. + return false; + } + default: + // LCOV_EXCL_START + UNREACHABLE("xrpl::isLedgerEntryOwner : object is not supported by sponsorship."); + return false; + // LCOV_EXCL_STOP + }; +} + +bool +isLedgerEntrySupportedBySponsorship(SLE const& sle) +{ + switch (sle.getType()) + { + case ltCHECK: + case ltESCROW: + case ltPAYCHAN: + case ltMPTOKEN: + case ltDELEGATE: + case ltDEPOSIT_PREAUTH: + case ltMPTOKEN_ISSUANCE: + case ltSIGNER_LIST: + case ltCREDENTIAL: + case ltRIPPLE_STATE: + return true; + default: + return false; + }; +} + +std::uint32_t +getLedgerEntryOwnerCount(SLE const& sle) +{ + switch (sle.getType()) + { + case ltORACLE: { + return calculateOracleReserve(sle.getFieldArray(sfPriceDataSeries)); + } + // Vaults require 2 owner counts (the vault and a pseudo-account) + case ltVAULT: + return 2; + case ltSIGNER_LIST: { + // Mirror SignerListSet's owner-count accounting so that create and + // delete agree. Modern lists (post-MultiSignReserve) carry the + // lsfOneOwnerCount flag and cost a single owner count. Legacy + // pre-MultiSignReserve lists cost 2 + signer_count owner counts + if (sle.isFlag(lsfOneOwnerCount)) + return 1; + return 2 + static_cast(sle.getFieldArray(sfSignerEntries).size()); + } + case ltACCOUNT_ROOT: + // LCOV_EXCL_START + UNREACHABLE("AccountRoots are not supported by object sponsorship."); + return 0; + // LCOV_EXCL_STOP + default: + return 1; + } +} + +SF_ACCOUNT const& +getLedgerEntrySponsorField(SLE const& sle, AccountID const& owner) +{ + switch (sle.getType()) + { + case ltRIPPLE_STATE: { + if (sle.isFlag(lsfHighReserve)) + { + auto const highAccount = sle.getFieldAmount(sfHighLimit).getIssuer(); + if (highAccount == owner) + return sfHighSponsor; + } + if (sle.isFlag(lsfLowReserve)) + { + auto const lowAccount = sle.getFieldAmount(sfLowLimit).getIssuer(); + if (lowAccount == owner) + return sfLowSponsor; + } + // LCOV_EXCL_START + UNREACHABLE("xrpl::getLedgerEntrySponsorField : unknown owner for RippleState"); + return sfSponsor; + // LCOV_EXCL_STOP + } + default: + return sfSponsor; + } +} + +} // namespace xrpl diff --git a/src/libxrpl/ledger/helpers/TokenHelpers.cpp b/src/libxrpl/ledger/helpers/TokenHelpers.cpp index b5fae0cb9d..09bb532ceb 100644 --- a/src/libxrpl/ledger/helpers/TokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/TokenHelpers.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -577,7 +578,7 @@ canAddHolding(ReadView const& view, Asset const& asset) TER addEmptyHolding( - ApplyView& view, + ApplyViewContext ctx, AccountID const& accountID, XRPAmount priorBalance, Asset const& asset, @@ -585,21 +586,21 @@ addEmptyHolding( { return std::visit( [&](TIss const& issue) -> TER { - return addEmptyHolding(view, accountID, priorBalance, issue, journal); + return addEmptyHolding(ctx, accountID, priorBalance, issue, journal); }, asset.value()); } TER removeEmptyHolding( - ApplyView& view, + ApplyViewContext ctx, AccountID const& accountID, Asset const& asset, beast::Journal journal) { return std::visit( [&](TIss const& issue) -> TER { - return removeEmptyHolding(view, accountID, issue, journal); + return removeEmptyHolding(ctx, accountID, issue, journal); }, asset.value()); } @@ -653,6 +654,7 @@ directSendNoFeeIOU( AccountID const& uReceiverID, STAmount const& saAmount, bool bCheckIssuer, + SLE::ref sponsorSle, beast::Journal j) { AccountID const& issuer = saAmount.getIssuer(); @@ -723,7 +725,12 @@ directSendNoFeeIOU( // Sender quality out is 0. { // Clear the reserve of the sender, possibly delete the line! - adjustOwnerCount(view, view.peek(keylet::account(uSenderID)), -1, j); + auto const currentSponsor = getLedgerEntryReserveSponsor( + view, sleRippleState, !bSenderHigh ? sfLowSponsor : sfHighSponsor); + decreaseOwnerCount(view, view.peek(keylet::account(uSenderID)), currentSponsor, 1, j); + + removeSponsorFromLedgerEntry( + sleRippleState, !bSenderHigh ? sfLowSponsor : sfHighSponsor); // Clear reserve flag. sleRippleState->clearFlag(senderReserveFlag); @@ -786,6 +793,7 @@ directSendNoFeeIOU( saReceiverLimit, 0, 0, + sponsorSle, j); } @@ -800,6 +808,7 @@ directSendNoLimitIOU( STAmount const& saAmount, STAmount& saActual, beast::Journal j, + SLE::ref sponsorSle, WaiveTransferFee waiveFee) { auto const& issuer = saAmount.getIssuer(); @@ -812,7 +821,8 @@ directSendNoLimitIOU( if (uSenderID == issuer || uReceiverID == issuer || issuer == noAccount()) { // Direct send: redeeming IOUs and/or sending own IOUs. - auto const ter = directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, false, j); + auto const ter = + directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, false, sponsorSle, j); if (!isTesSuccess(ter)) return ter; saActual = saAmount; @@ -830,10 +840,12 @@ directSendNoLimitIOU( << to_string(uReceiverID) << " : deliver=" << saAmount.getFullText() << " cost=" << saActual.getFullText(); - TER terResult = directSendNoFeeIOU(view, issuer, uReceiverID, saAmount, true, j); + TER terResult = directSendNoFeeIOU(view, issuer, uReceiverID, saAmount, true, sponsorSle, j); if (tesSUCCESS == terResult) - terResult = directSendNoFeeIOU(view, uSenderID, issuer, saActual, true, j); + { + terResult = directSendNoFeeIOU(view, uSenderID, issuer, saActual, true, sponsorSle, j); + } return terResult; } @@ -876,7 +888,8 @@ directSendNoLimitMultiIOU( if (senderID == issuer || receiverID == issuer || issuer == noAccount()) { // Direct send: redeeming IOUs and/or sending own IOUs. - if (auto const ter = directSendNoFeeIOU(view, senderID, receiverID, amount, false, j); + if (auto const ter = + directSendNoFeeIOU(view, senderID, receiverID, amount, false, {}, j); !isTesSuccess(ter)) return ter; actual += amount; @@ -900,14 +913,14 @@ directSendNoLimitMultiIOU( << to_string(receiverID) << " : deliver=" << amount.getFullText() << " cost=" << actual.getFullText(); - if (TER const terResult = directSendNoFeeIOU(view, issuer, receiverID, amount, true, j)) + if (TER const terResult = directSendNoFeeIOU(view, issuer, receiverID, amount, true, {}, j)) return terResult; } if (senderID != issuer && takeFromSender) { if (TER const terResult = - directSendNoFeeIOU(view, senderID, issuer, takeFromSender, true, j)) + directSendNoFeeIOU(view, senderID, issuer, takeFromSender, true, {}, j)) return terResult; } @@ -921,6 +934,7 @@ accountSendIOU( AccountID const& uReceiverID, STAmount const& saAmount, beast::Journal j, + SLE::ref sponsorSle, WaiveTransferFee waiveFee) { if (view.rules().enabled(fixAMMv1_1)) @@ -952,7 +966,8 @@ accountSendIOU( JLOG(j.trace()) << "accountSendIOU: " << to_string(uSenderID) << " -> " << to_string(uReceiverID) << " : " << saAmount.getFullText(); - return directSendNoLimitIOU(view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee); + return directSendNoLimitIOU( + view, uSenderID, uReceiverID, saAmount, saActual, j, sponsorSle, waiveFee); } /* XRP send which does not check reserve and can do pure adjustment. @@ -1481,7 +1496,7 @@ directSendNoFee( { return saAmount.asset().visit( [&](Issue const&) { - return directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, bCheckIssuer, j); + return directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, bCheckIssuer, {}, j); }, [&](MPTIssue const&) { XRPL_ASSERT(!bCheckIssuer, "xrpl::directSendNoFee : not checking issuer"); @@ -1496,12 +1511,13 @@ accountSend( AccountID const& uReceiverID, STAmount const& saAmount, beast::Journal j, + SLE::ref sponsorSle, WaiveTransferFee waiveFee, AllowMPTOverflow allowOverflow) { return saAmount.asset().visit( [&](Issue const&) { - return accountSendIOU(view, uSenderID, uReceiverID, saAmount, j, waiveFee); + return accountSendIOU(view, uSenderID, uReceiverID, saAmount, j, sponsorSle, waiveFee); }, [&](MPTIssue const&) { return accountSendMPT( diff --git a/src/libxrpl/nodestore/DecodedBlob.cpp b/src/libxrpl/nodestore/DecodedBlob.cpp index fe07252f23..9740462ae8 100644 --- a/src/libxrpl/nodestore/DecodedBlob.cpp +++ b/src/libxrpl/nodestore/DecodedBlob.cpp @@ -12,7 +12,7 @@ namespace xrpl::NodeStore { -DecodedBlob::DecodedBlob(void const* key, void const* value, int valueBytes) +DecodedBlob::DecodedBlob(void const* key, void const* value, int valueBytes) : key_(key) { /* Data format: @@ -23,10 +23,6 @@ DecodedBlob::DecodedBlob(void const* key, void const* value, int valueBytes) 9...end The body of the object data */ - success_ = false; - key_ = key; - objectType_ = NodeObjectType::Unknown; - objectData_ = nullptr; dataBytes_ = std::max(0, valueBytes - 9); // VFALCO NOTE What about bytes 4 through 7 inclusive? diff --git a/src/libxrpl/nodestore/backend/NullFactory.cpp b/src/libxrpl/nodestore/backend/NullFactory.cpp index e36b13a2e1..0c76cb9938 100644 --- a/src/libxrpl/nodestore/backend/NullFactory.cpp +++ b/src/libxrpl/nodestore/backend/NullFactory.cpp @@ -81,7 +81,9 @@ public: { } - /** Returns the number of file descriptors the backend expects to need */ + /** + * Returns the number of file descriptors the backend expects to need + */ [[nodiscard]] int fdRequired() const override { diff --git a/src/libxrpl/nodestore/backend/RocksDBFactory.cpp b/src/libxrpl/nodestore/backend/RocksDBFactory.cpp index bcf4ba4a49..673b0daae0 100644 --- a/src/libxrpl/nodestore/backend/RocksDBFactory.cpp +++ b/src/libxrpl/nodestore/backend/RocksDBFactory.cpp @@ -419,7 +419,9 @@ public: storeBatch(batch); } - /** Returns the number of file descriptors the backend expects to need */ + /** + * Returns the number of file descriptors the backend expects to need + */ [[nodiscard]] int fdRequired() const override { diff --git a/src/libxrpl/protocol/AccountID.cpp b/src/libxrpl/protocol/AccountID.cpp index 6050144a8e..c6a5226566 100644 --- a/src/libxrpl/protocol/AccountID.cpp +++ b/src/libxrpl/protocol/AccountID.cpp @@ -21,7 +21,9 @@ namespace xrpl { namespace detail { -/** Caches the base58 representations of AccountIDs */ +/** + * Caches the base58 representations of AccountIDs + */ class AccountIdCache { private: diff --git a/src/libxrpl/protocol/BuildInfo.cpp b/src/libxrpl/protocol/BuildInfo.cpp index 820936a22d..793ddb18d2 100644 --- a/src/libxrpl/protocol/BuildInfo.cpp +++ b/src/libxrpl/protocol/BuildInfo.cpp @@ -23,7 +23,7 @@ namespace { //------------------------------------------------------------------------------ // clang-format off // NOLINTNEXTLINE(readability-identifier-naming) -char const* const versionString = "3.3.0-b0" +char const* const versionString = "3.3.0-b1" // clang-format on ; diff --git a/src/libxrpl/protocol/Feature.cpp b/src/libxrpl/protocol/Feature.cpp index 059abe2996..2a8476dea8 100644 --- a/src/libxrpl/protocol/Feature.cpp +++ b/src/libxrpl/protocol/Feature.cpp @@ -154,7 +154,9 @@ public: uint256 registerFeature(std::string const& name, Supported support, VoteBehavior vote); - /** Tell FeatureCollections when registration is complete. */ + /** + * Tell FeatureCollections when registration is complete. + */ bool registrationIsDone(); @@ -167,30 +169,38 @@ public: std::string featureToName(uint256 const& f) const; - /** All amendments that are registered within the table. */ + /** + * All amendments that are registered within the table. + */ std::map const& allAmendments() const { return all_; } - /** Amendments that this server supports. - Whether they are enabled depends on the Rules defined in the validated - ledger */ + /** + * Amendments that this server supports. + * Whether they are enabled depends on the Rules defined in the validated + * ledger + */ std::map const& supportedAmendments() const { return supported_; } - /** Amendments that this server WON'T vote for by default. */ + /** + * Amendments that this server WON'T vote for by default. + */ std::size_t numDownVotedAmendments() const { return downVotes_; } - /** Amendments that this server WILL vote for by default. */ + /** + * Amendments that this server WILL vote for by default. + */ std::size_t numUpVotedAmendments() const { @@ -271,7 +281,9 @@ FeatureCollections::registerFeature(std::string const& name, Supported support, logicError("Duplicate feature registration"); } -/** Tell FeatureCollections when registration is complete. */ +/** + * Tell FeatureCollections when registration is complete. + */ bool FeatureCollections::registrationIsDone() { @@ -313,30 +325,38 @@ FeatureCollections gFeatureCollections; } // namespace -/** All amendments libxrpl knows of. */ +/** + * All amendments libxrpl knows of. + */ std::map const& allAmendments() { return gFeatureCollections.allAmendments(); } -/** Amendments that this server supports. - Whether they are enabled depends on the Rules defined in the validated - ledger */ +/** + * Amendments that this server supports. + * Whether they are enabled depends on the Rules defined in the validated + * ledger + */ std::map const& detail::supportedAmendments() { return gFeatureCollections.supportedAmendments(); } -/** Amendments that this server won't vote for by default. */ +/** + * Amendments that this server won't vote for by default. + */ std::size_t detail::numDownVotedAmendments() { return gFeatureCollections.numDownVotedAmendments(); } -/** Amendments that this server will vote for by default. */ +/** + * Amendments that this server will vote for by default. + */ std::size_t detail::numUpVotedAmendments() { @@ -365,7 +385,9 @@ retireFeature(std::string const& name) return registerFeature(name, Supported::Yes, VoteBehavior::Obsolete); } -/** Tell FeatureCollections when registration is complete. */ +/** + * Tell FeatureCollections when registration is complete. + */ bool registrationIsDone() { diff --git a/src/libxrpl/protocol/Indexes.cpp b/src/libxrpl/protocol/Indexes.cpp index 214ec76ba9..dc6f560f08 100644 --- a/src/libxrpl/protocol/Indexes.cpp +++ b/src/libxrpl/protocol/Indexes.cpp @@ -32,23 +32,24 @@ namespace xrpl { -/** Type-specific prefix for calculating ledger indices. - - The identifier for a given object within the ledger is calculated based - on some object-specific parameters. To ensure that different types of - objects have different indices, even if they happen to use the same set - of parameters, we use "tagged hashing" by adding a type-specific prefix. - - @note These values are part of the protocol and *CANNOT* be arbitrarily - changed. If they were, on-ledger objects may no longer be able to - be located or addressed. - - Additions to this list are OK, but changing existing entries to - assign them a different values should never be needed. - - Entries that are removed should be moved to the bottom of the enum - and marked as [[deprecated]] to prevent accidental reuse. -*/ +/** + * Type-specific prefix for calculating ledger indices. + * + * The identifier for a given object within the ledger is calculated based + * on some object-specific parameters. To ensure that different types of + * objects have different indices, even if they happen to use the same set + * of parameters, we use "tagged hashing" by adding a type-specific prefix. + * + * @note These values are part of the protocol and *CANNOT* be arbitrarily + * changed. If they were, on-ledger objects may no longer be able to + * be located or addressed. + * + * Additions to this list are OK, but changing existing entries to + * assign them a different values should never be needed. + * + * Entries that are removed should be moved to the bottom of the enum + * and marked as [[deprecated]] to prevent accidental reuse. + */ enum class LedgerNameSpace : std::uint16_t { Account = 'a', DirNode = 'd', @@ -84,6 +85,7 @@ enum class LedgerNameSpace : std::uint16_t { Vault = 'V', LoanBroker = 'l', // lower-case L Loan = 'L', + Sponsorship = '>', // No longer used or supported. Left here to reserve the space to avoid accidental reuse. Contract [[deprecated]] = 'c', @@ -169,7 +171,12 @@ std::uint64_t getQuality(uint256 const& uBase) { // VFALCO [base_uint] This assumes a certain storage format - return boost::endian::big_to_native(((std::uint64_t*)uBase.end())[-1]); + // + // Load the final 8 bytes as a big-endian integer. load_big_u64 reads + // through unaligned byte storage (via memcpy) and applies the endian + // conversion, avoiding the alignment/strict-aliasing UB of casting the + // unsigned char* returned by end() to a std::uint64_t*. + return boost::endian::load_big_u64(uBase.end() - 8); } uint256 @@ -295,8 +302,11 @@ quality(Keylet const& k, std::uint64_t q) noexcept // for indexes. uint256 x = k.key; - // FIXME This is ugly and we can and should do better... - ((std::uint64_t*)x.end())[-1] = boost::endian::native_to_big(q); + // Store the quality as a big-endian integer in the final 8 bytes. + // store_big_u64 writes through unaligned byte storage (via memcpy) and + // applies the endian conversion, avoiding the alignment/strict-aliasing UB + // of casting the unsigned char* returned by end() to a std::uint64_t*. + boost::endian::store_big_u64(x.end() - 8, q); return {ltDIR_NODE, x}; } @@ -335,6 +345,12 @@ signerList(AccountID const& account) noexcept return signerList(account, 0); } +Keylet +sponsorship(AccountID const& sponsor, AccountID const& sponsee) noexcept +{ + return {ltSPONSORSHIP, indexHash(LedgerNameSpace::Sponsorship, sponsor, sponsee)}; +} + Keylet check(AccountID const& id, std::uint32_t seq) noexcept { diff --git a/src/libxrpl/protocol/InnerObjectFormats.cpp b/src/libxrpl/protocol/InnerObjectFormats.cpp index 66b2822a42..0bdb217771 100644 --- a/src/libxrpl/protocol/InnerObjectFormats.cpp +++ b/src/libxrpl/protocol/InnerObjectFormats.cpp @@ -160,6 +160,14 @@ InnerObjectFormats::InnerObjectFormats() {sfTxnSignature, SoeOptional}, {sfSigners, SoeOptional}, }); + + add(sfSponsorSignature.jsonName.cStr(), + sfSponsorSignature.getCode(), + { + {sfSigningPubKey, SoeOptional}, + {sfTxnSignature, SoeOptional}, + {sfSigners, SoeOptional}, + }); } InnerObjectFormats const& diff --git a/src/libxrpl/protocol/LedgerFormats.cpp b/src/libxrpl/protocol/LedgerFormats.cpp index 8b91bb7930..a29ea49266 100644 --- a/src/libxrpl/protocol/LedgerFormats.cpp +++ b/src/libxrpl/protocol/LedgerFormats.cpp @@ -15,6 +15,7 @@ LedgerFormats::getCommonFields() {sfLedgerIndex, SoeOptional}, {sfLedgerEntryType, SoeRequired}, {sfFlags, SoeRequired}, + {sfSponsor, SoeOptional}, }; return kCommonFields; } diff --git a/src/libxrpl/protocol/PublicKey.cpp b/src/libxrpl/protocol/PublicKey.cpp index 97948fcae3..cb6ea9e851 100644 --- a/src/libxrpl/protocol/PublicKey.cpp +++ b/src/libxrpl/protocol/PublicKey.cpp @@ -96,18 +96,19 @@ sliceToHex(Slice const& slice) return s; } -/** Determine whether a signature is canonical. - Canonical signatures are important to protect against signature morphing - attacks. - @param vSig the signature data - @param sigLen the length of the signature - @param strict_param whether to enforce strictly canonical semantics - - @note For more details please see: - https://xrpl.org/transaction-malleability.html - https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623 - https://github.com/sipa/bitcoin/commit/58bc86e37fda1aec270bccb3df6c20fbd2a6591c -*/ +/** + * Determine whether a signature is canonical. + * Canonical signatures are important to protect against signature morphing + * attacks. + * @param vSig the signature data + * @param sigLen the length of the signature + * @param strict_param whether to enforce strictly canonical semantics + * + * @note For more details please see: + * https://xrpl.org/transaction-malleability.html + * https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623 + * https://github.com/sipa/bitcoin/commit/58bc86e37fda1aec270bccb3df6c20fbd2a6591c + */ std::optional ecdsaCanonicality(Slice const& sig) { diff --git a/src/libxrpl/protocol/Rules.cpp b/src/libxrpl/protocol/Rules.cpp index e0968ea868..d71bb77f66 100644 --- a/src/libxrpl/protocol/Rules.cpp +++ b/src/libxrpl/protocol/Rules.cpp @@ -39,22 +39,30 @@ setCurrentTransactionRules(std::optional r) // Push the appropriate setting, instead of having the class pull every time // the value is needed. That could get expensive fast. - // If any new conditions with new amendments are added, those amendments must also be added to - // useRulesGuards. - bool const enableVaultNumbers = - !r || (r->enabled(featureSingleAssetVault) || r->enabled(featureLendingProtocol)); - bool const enableCuspRoundingFix = !r || r->enabled(fixCleanup3_2_0); - XRPL_ASSERT( - !r || useRulesGuards(*r) == (enableCuspRoundingFix || enableVaultNumbers), - "setCurrentTransactionRules : rule decisions match"); - // Declare the range this way to keep clang-tidy from complaining - auto const range = [enableCuspRoundingFix, enableVaultNumbers]() { - if (enableVaultNumbers) + auto const range = [&r]() { + // If any new conditions with new amendments are added to "enableLargeNumbers", those + // amendments must also be added to useRulesGuards. + bool const enableLargeNumbers = + !r || (r->enabled(featureSingleAssetVault) || r->enabled(featureLendingProtocol)); + // If enableLargeNumbers is true, then useRulesGuard must also return true. + // However, the reverse is not true. Other amendments can cause the rules guard to be used, + // even though large numbers are _not_ used. + XRPL_ASSERT( + !r || !enableLargeNumbers || useRulesGuards(*r), + "setCurrentTransactionRules : rule decisions match"); + + if (enableLargeNumbers) { - if (enableCuspRoundingFix) + static_assert( + MantissaRange::MantissaScale::Large == MantissaRange::MantissaScale::Large330); + if (!r || r->enabled(fixCleanup3_3_0)) { - return MantissaRange::MantissaScale::Large; + return MantissaRange::MantissaScale::Large330; + } + if (r->enabled(fixCleanup3_2_0)) + { + return MantissaRange::MantissaScale::Large320; } return MantissaRange::MantissaScale::LargeLegacy; } @@ -69,14 +77,14 @@ bool useRulesGuards(Rules const& rules) { // The list of amendments used here - to decide whether to create a RulesGuard - must be a - // superset of the list used to figure out which mantissa scale to use in - // setCurrentTransactionRules. Additional amendments can be added if desired. + // superset of the list used to determine "enableLargeNumbers" in setCurrentTransactionRules. + // Additional amendments can be added if desired. // // As soon as any one of these amendments is retired, this whole function can be removed, along // with createGuards, and any other callers, and the first set of guards can be created directly // at the call site, without using optional. - return rules.enabled(fixCleanup3_2_0) || rules.enabled(featureSingleAssetVault) || - rules.enabled(featureLendingProtocol); + return rules.enabled(featureSingleAssetVault) || rules.enabled(featureLendingProtocol) || + rules.enabled(fixCleanup3_2_0) || rules.enabled(fixCleanup3_3_0); } void @@ -87,7 +95,8 @@ createGuards( { if (useRulesGuards(rules)) { - // raii classes for the current ledger rules. + // raii classes for the current ledger rules. If the rules are set, the MantissaRange will + // be updated, too. rulesGuard.emplace(rules); } else diff --git a/src/libxrpl/protocol/SOTemplate.cpp b/src/libxrpl/protocol/SOTemplate.cpp index 708fd465e2..171f96e1ed 100644 --- a/src/libxrpl/protocol/SOTemplate.cpp +++ b/src/libxrpl/protocol/SOTemplate.cpp @@ -21,11 +21,12 @@ SOTemplate::SOTemplate( } SOTemplate::SOTemplate(std::vector uniqueFields, std::vector commonFields) - : indices_(SField::getNumFields() + 1, -1) // Unmapped indices == -1 + : elements_(std::move(uniqueFields)) + , indices_(SField::getNumFields() + 1, -1) // Unmapped indices == -1 { // Add all SOElements. // - elements_ = std::move(uniqueFields); + std::ranges::move(commonFields, std::back_inserter(elements_)); // Validate and index elements_. diff --git a/src/libxrpl/protocol/STNumber.cpp b/src/libxrpl/protocol/STNumber.cpp index 79f7655869..bd7f67649b 100644 --- a/src/libxrpl/protocol/STNumber.cpp +++ b/src/libxrpl/protocol/STNumber.cpp @@ -88,7 +88,6 @@ STNumber::add(Serializer& s) const } else { -#if !NDEBUG // There are circumstances where an already-rounded Number is // serialized without being touched by a transactor, and thus // without an asset. We can't know if it's rounded, because it could @@ -96,11 +95,9 @@ STNumber::add(Serializer& s) const // Json. Regardless, the only time we should be serializing an // STNumber is when the scale is large. XRPL_ASSERT_PARTS( - Number::getMantissaScale() == MantissaRange::MantissaScale::LargeLegacy || - Number::getMantissaScale() == MantissaRange::MantissaScale::Large, + Number::getMantissaScale() != MantissaRange::MantissaScale::Small, "xrpl::STNumber::add", "STNumber only used with large mantissa scale"); -#endif } } diff --git a/src/libxrpl/protocol/STObject.cpp b/src/libxrpl/protocol/STObject.cpp index 869700baed..e86b3fb6e6 100644 --- a/src/libxrpl/protocol/STObject.cpp +++ b/src/libxrpl/protocol/STObject.cpp @@ -634,16 +634,16 @@ STObject::getAccountID(SField const& field) const } AccountID -STObject::getFeePayer() const +STObject::getInitiator() const { - // If sfDelegate is present, the delegate account is the payer + // If sfDelegate is present, the delegate account is the initiator // note: if a delegate is specified, its authorization to act on behalf of the account is // enforced in `Transactor::invokeCheckPermission` // cryptographic signature validity is checked separately (e.g., in `Transactor::checkSign`) if (isFieldPresent(sfDelegate)) return getAccountID(sfDelegate); - // Default payer + // Default initiator return getAccountID(sfAccount); } diff --git a/src/libxrpl/protocol/STParsedJSON.cpp b/src/libxrpl/protocol/STParsedJSON.cpp index d3342ef728..33ca5424d1 100644 --- a/src/libxrpl/protocol/STParsedJSON.cpp +++ b/src/libxrpl/protocol/STParsedJSON.cpp @@ -69,6 +69,17 @@ toUnsigned(U2 value) return static_cast(value); } +static std::string +joinName(std::string const& jsonName, std::string const& fieldName) +{ + std::string result; + result.reserve(jsonName.size() + 1 + fieldName.size()); + result += jsonName; + result += '.'; + result += fieldName; + return result; +} + // LCOV_EXCL_START static inline std::string makeName(std::string const& object, std::string const& field) @@ -76,7 +87,7 @@ makeName(std::string const& object, std::string const& field) if (field.empty()) return object; - return object + "." + field; + return joinName(object, field); } static inline json::Value @@ -1036,8 +1047,8 @@ parseObject( try { - auto ret = - parseObject(jsonName + "." + fieldName, value, field, depth + 1, error); + auto ret = parseObject( + joinName(jsonName, fieldName), value, field, depth + 1, error); if (!ret) return std::nullopt; data.emplaceBack(std::move(*ret)); @@ -1054,8 +1065,8 @@ parseObject( case STI_ARRAY: try { - auto array = - parseArray(jsonName + "." + fieldName, value, field, depth + 1, error); + auto array = parseArray( + joinName(jsonName, fieldName), value, field, depth + 1, error); if (!array.has_value()) return std::nullopt; data.emplaceBack(std::move(*array)); diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index 9f3c6738ae..be4ba2e8e5 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -66,9 +67,9 @@ getTxFormat(TxType type) return format; } -STTx::STTx(STObject&& object) : STObject(std::move(object)) +STTx::STTx(STObject&& object) + : STObject(std::move(object)), txType_(safeCast(getFieldU16(sfTransactionType))) { - txType_ = safeCast(getFieldU16(sfTransactionType)); applyTemplate(getTxFormat(txType_)->getSOTemplate()); // may throw tid_ = getHash(HashPrefix::TransactionId); buildBatchTxnIds(); @@ -100,6 +101,9 @@ STTx::STTx(TxType type, std::function assembler) : STObject(sfT assembler(*this); + // txType_ must be read after the object is assembled, so this cannot be a + // member initializer. + // NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer) txType_ = safeCast(getFieldU16(sfTransactionType)); if (txType_ != type) @@ -268,6 +272,13 @@ STTx::checkSign(Rules const& rules) const return std::unexpected("Counterparty: " + ret.error()); } + if (isFieldPresent(sfSponsorSignature)) + { + auto const sponsorSignatureObj = getFieldObject(sfSponsorSignature); + if (auto const ret = checkSign(rules, sponsorSignatureObj); !ret) + return std::unexpected("Sponsor: " + ret.error()); + } + // Verify the batch signer signatures here too, so they are cached with the // rest of signature checking (checkValidity / SF_SIGGOOD) and stay out of // the transaction engine. Gated on a batch (batchTxnIds_ seated) that @@ -548,7 +559,7 @@ STTx::checkMultiSign(Rules const& rules, STObject const& sigObject) const // For delegated transactions sfDelegate is the account whose signer list is checked, // the delegate account itself can not be among the signers. auto const txnAccountID = - &sigObject != this ? std::nullopt : std::optional(getFeePayer()); + &sigObject != this ? std::nullopt : std::optional(getInitiator()); // We can ease the computational load inside the loop a bit by // pre-constructing part of the data that we hash. Fill a Serializer @@ -600,6 +611,15 @@ STTx::getBatchTransactionIDs() const return *batchTxnIds_; } +AccountID +STTx::getFeePayerID() const +{ + if (isFieldPresent(sfSponsor) && ((getFieldU32(sfSponsorFlags) & spfSponsorFee) != 0u)) + return at(sfSponsor); + + return getInitiator(); +} + //------------------------------------------------------------------------------ static bool diff --git a/src/libxrpl/protocol/SecretKey.cpp b/src/libxrpl/protocol/SecretKey.cpp index f33b1871e1..7713911ab4 100644 --- a/src/libxrpl/protocol/SecretKey.cpp +++ b/src/libxrpl/protocol/SecretKey.cpp @@ -99,23 +99,24 @@ deriveDeterministicRootKey(Seed const& seed) } //------------------------------------------------------------------------------ -/** Produces a sequence of secp256k1 key pairs. - - The reference implementation of the XRP Ledger uses a custom derivation - algorithm which enables the derivation of an entire family of secp256k1 - keypairs from a single 128-bit seed. The algorithm predates widely-used - standards like BIP-32 and BIP-44. - - Important note to implementers: - - Using this algorithm is not required: all valid secp256k1 keypairs will - work correctly. Third party implementations can use whatever mechanisms - they prefer. However, implementers of wallets or other tools that allow - users to use existing accounts should consider at least supporting this - derivation technique to make it easier for users to 'import' accounts. - - For more details, please check out: - https://xrpl.org/cryptographic-keys.html#secp256k1-key-derivation +/** + * Produces a sequence of secp256k1 key pairs. + * + * The reference implementation of the XRP Ledger uses a custom derivation + * algorithm which enables the derivation of an entire family of secp256k1 + * keypairs from a single 128-bit seed. The algorithm predates widely-used + * standards like BIP-32 and BIP-44. + * + * Important note to implementers: + * + * Using this algorithm is not required: all valid secp256k1 keypairs will + * work correctly. Third party implementations can use whatever mechanisms + * they prefer. However, implementers of wallets or other tools that allow + * users to use existing accounts should consider at least supporting this + * derivation technique to make it easier for users to 'import' accounts. + * + * For more details, please check out: + * https://xrpl.org/cryptographic-keys.html#secp256k1-key-derivation */ class Generator { @@ -177,7 +178,9 @@ public: secureErase(generator_.data(), generator_.size()); } - /** Generate the nth key pair. */ + /** + * Generate the nth key pair. + */ std::pair operator()(std::size_t ordinal) const { diff --git a/src/libxrpl/protocol/Serializer.cpp b/src/libxrpl/protocol/Serializer.cpp index 1eda04705f..80ecdee6c8 100644 --- a/src/libxrpl/protocol/Serializer.cpp +++ b/src/libxrpl/protocol/Serializer.cpp @@ -99,7 +99,7 @@ int Serializer::addRaw(void const* ptr, int len) { int const ret = data_.size(); - data_.insert(data_.end(), (char const*)ptr, ((char const*)ptr) + len); + data_.insert(data_.end(), static_cast(ptr), static_cast(ptr) + len); return ret; } diff --git a/src/libxrpl/protocol/TER.cpp b/src/libxrpl/protocol/TER.cpp index a6f8192a2f..c2167d58ce 100644 --- a/src/libxrpl/protocol/TER.cpp +++ b/src/libxrpl/protocol/TER.cpp @@ -107,6 +107,7 @@ transResults() MAKE_ERROR(tecPSEUDO_ACCOUNT, "This operation is not allowed against a pseudo-account."), MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."), MAKE_ERROR(tecBAD_PROOF, "Proof cannot be verified"), + MAKE_ERROR(tecNO_SPONSOR_PERMISSION, "Sponsor has not authorized this transaction."), MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."), MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."), @@ -220,6 +221,7 @@ transResults() MAKE_ERROR(terADDRESS_COLLISION, "Failed to allocate an unique account address."), MAKE_ERROR(terNO_DELEGATE_PERMISSION, "Delegated account lacks permission to perform this transaction."), MAKE_ERROR(terLOCKED, "Fund is locked."), + MAKE_ERROR(terNO_PERMISSION, "No permission to perform requested operation."), MAKE_ERROR(tesSUCCESS, "The transaction was applied. Only final in a validated ledger."), }; diff --git a/src/libxrpl/protocol/TxFormats.cpp b/src/libxrpl/protocol/TxFormats.cpp index b926bdf0e5..e4d4c4b03c 100644 --- a/src/libxrpl/protocol/TxFormats.cpp +++ b/src/libxrpl/protocol/TxFormats.cpp @@ -30,6 +30,9 @@ TxFormats::getCommonFields() {sfSigners, SoeOptional}, // submit_multisigned {sfNetworkID, SoeOptional}, {sfDelegate, SoeOptional}, + {sfSponsor, SoeOptional}, + {sfSponsorFlags, SoeOptional}, + {sfSponsorSignature, SoeOptional}, }; return kCommonFields; } diff --git a/src/libxrpl/protocol/TxMeta.cpp b/src/libxrpl/protocol/TxMeta.cpp index 0373706e84..b743598a76 100644 --- a/src/libxrpl/protocol/TxMeta.cpp +++ b/src/libxrpl/protocol/TxMeta.cpp @@ -23,16 +23,16 @@ namespace xrpl { TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj) - : transactionID_(txid), ledgerSeq_(ledger), nodes_(obj.getFieldArray(sfAffectedNodes)) + : transactionID_(txid) + , ledgerSeq_(ledger) + , index_(obj.getFieldU32(sfTransactionIndex)) + , result_(obj.getFieldU8(sfTransactionResult)) + , nodes_([&obj] { + auto const affectedNodes = dynamic_cast(obj.peekAtPField(sfAffectedNodes)); + XRPL_ASSERT(affectedNodes, "xrpl::TxMeta::TxMeta(STObject) : type cast succeeded"); + return affectedNodes != nullptr ? *affectedNodes : obj.getFieldArray(sfAffectedNodes); + }()) { - result_ = obj.getFieldU8(sfTransactionResult); - index_ = obj.getFieldU32(sfTransactionIndex); - - auto affectedNodes = dynamic_cast(obj.peekAtPField(sfAffectedNodes)); - XRPL_ASSERT(affectedNodes, "xrpl::TxMeta::TxMeta(STObject) : type cast succeeded"); - if (affectedNodes != nullptr) - nodes_ = *affectedNodes; - setAdditionalFields(obj); } diff --git a/src/libxrpl/protocol/tokens.cpp b/src/libxrpl/protocol/tokens.cpp index 21984c67e7..bd0f54c3ae 100644 --- a/src/libxrpl/protocol/tokens.cpp +++ b/src/libxrpl/protocol/tokens.cpp @@ -160,15 +160,16 @@ digest2(Args const&... args) return digest(digest(args...)); } -/** Calculate a 4-byte checksum of the data - - The checksum is calculated as the first 4 bytes - of the SHA256 digest of the message. This is added - to the base58 encoding of identifiers to detect - user error in data entry. - - @note This checksum algorithm is part of the client API -*/ +/** + * Calculate a 4-byte checksum of the data + * + * The checksum is calculated as the first 4 bytes + * of the SHA256 digest of the message. This is added + * to the base58 encoding of identifiers to detect + * user error in data entry. + * + * @note This checksum algorithm is part of the client API + */ static void checksum(void* out, void const* message, std::size_t size) { diff --git a/src/libxrpl/rdb/SociDB.cpp b/src/libxrpl/rdb/SociDB.cpp index 06e58d373f..7354ef9b23 100644 --- a/src/libxrpl/rdb/SociDB.cpp +++ b/src/libxrpl/rdb/SociDB.cpp @@ -188,14 +188,15 @@ convert(std::string const& from, soci::blob& to) namespace { -/** Run a thread to checkpoint the write ahead log (wal) for - the given soci::session every 1000 pages. This is only implemented - for sqlite databases. - - Note: According to: https://www.sqlite.org/wal.html#ckpt this - is the default behavior of sqlite. We may be able to remove this - class. -*/ +/** + * Run a thread to checkpoint the write ahead log (wal) for + * the given soci::session every 1000 pages. This is only implemented + * for sqlite databases. + * + * Note: According to: https://www.sqlite.org/wal.html#ckpt this + * is the default behavior of sqlite. We may be able to remove this + * class. + */ class WALCheckpointer : public Checkpointer { @@ -213,6 +214,12 @@ public: if (auto [conn, keepAlive] = getConnection(); conn) { (void)keepAlive; + // The checkpointer is identified to the C callback by an integer id + // (resolved via checkpointerFromId) rather than a raw `this`, so it + // cannot dangle if the checkpointer is destroyed. Passing the id + // through sqlite's void* user-data requires an integer-to-pointer + // cast. + // NOLINTNEXTLINE(performance-no-int-to-ptr) sqlite_api::sqlite3_wal_hook(conn, &sqliteWALHook, reinterpret_cast(id_)); } } diff --git a/src/libxrpl/shamap/SHAMap.cpp b/src/libxrpl/shamap/SHAMap.cpp index 8a521f6a47..2483e6f6e1 100644 --- a/src/libxrpl/shamap/SHAMap.cpp +++ b/src/libxrpl/shamap/SHAMap.cpp @@ -838,6 +838,7 @@ SHAMap::getHash() const auto hash = root_->getHash(); if (hash.isZero()) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) const_cast(*this).unshare(); hash = root_->getHash(); } @@ -918,17 +919,18 @@ SHAMap::fetchRoot(SHAMapHash const& hash, SHAMapSyncFilter const* filter) return false; } -/** Replace a node with a shareable node. - - This code handles two cases: - - 1) An unshared, unshareable node needs to be made shareable - so immutable SHAMap's can have references to it. - 2) An unshareable node is shared. This happens when you make - a mutable snapshot of a mutable SHAMap. - - @note The node must have already been unshared by having the caller - first call SHAMapTreeNode::unshare(). +/** + * Replace a node with a shareable node. + * + * This code handles two cases: + * + * 1) An unshared, unshareable node needs to be made shareable + * so immutable SHAMap's can have references to it. + * 2) An unshareable node is shared. This happens when you make + * a mutable snapshot of a mutable SHAMap. + * + * @note The node must have already been unshared by having the caller + * first call SHAMapTreeNode::unshare(). */ SHAMapTreeNodePtr SHAMap::writeNode(NodeObjectType t, SHAMapTreeNodePtr node) const diff --git a/src/libxrpl/shamap/SHAMapSync.cpp b/src/libxrpl/shamap/SHAMapSync.cpp index 1f38049abe..cc30426f9d 100644 --- a/src/libxrpl/shamap/SHAMapSync.cpp +++ b/src/libxrpl/shamap/SHAMapSync.cpp @@ -300,10 +300,11 @@ SHAMap::gmnProcessDeferredReads(MissingNodes& mn) mn.deferred = 0; } -/** Get a list of node IDs and hashes for nodes that are part of this SHAMap - but not available locally. The filter can hold alternate sources of - nodes that are not permanently stored locally -*/ +/** + * Get a list of node IDs and hashes for nodes that are part of this SHAMap + * but not available locally. The filter can hold alternate sources of + * nodes that are not permanently stored locally + */ std::vector> SHAMap::getMissingNodes(int max, SHAMapSyncFilter const* filter) { @@ -720,7 +721,8 @@ SHAMap::deepCompare(SHAMap& other) const return true; } -/** Does this map have this inner node? +/** + * Does this map have this inner node? */ bool SHAMap::hasInnerNode(SHAMapNodeID const& targetNodeID, SHAMapHash const& targetNodeHash) const @@ -742,7 +744,8 @@ SHAMap::hasInnerNode(SHAMapNodeID const& targetNodeID, SHAMapHash const& targetN return (node->isInner()) && (node->getHash() == targetNodeHash); } -/** Does this map have this leaf node? +/** + * Does this map have this leaf node? */ bool SHAMap::hasLeafNode(uint256 const& tag, SHAMapHash const& targetNodeHash) const diff --git a/src/libxrpl/tx/Transactor.cpp b/src/libxrpl/tx/Transactor.cpp index 0f6988543e..4b562692d7 100644 --- a/src/libxrpl/tx/Transactor.cpp +++ b/src/libxrpl/tx/Transactor.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,7 @@ #include #include +#include #include #include #include @@ -54,7 +56,9 @@ namespace xrpl { -/** Performs early sanity checks on the txid */ +/** + * Performs early sanity checks on the txid + */ NotTEC preflight0(PreflightContext const& ctx, std::uint32_t flagMask) { @@ -109,7 +113,8 @@ preflight0(PreflightContext const& ctx, std::uint32_t flagMask) namespace detail { -/** Checks the validity of the transactor signing key. +/** + * Checks the validity of the transactor signing key. * * Normally called from preflight1. */ @@ -167,7 +172,61 @@ preflightCheckSimulateKeys(ApplyFlags flags, STObject const& sigObject, beast::J } // namespace detail -/** Performs early sanity checks on the account and fee fields */ +static NotTEC +preflight1Sponsor(PreflightContext const& ctx) +{ + bool const hasSponsor = ctx.tx.isFieldPresent(sfSponsor); + bool const hasSponsorFlags = ctx.tx.isFieldPresent(sfSponsorFlags); + bool const hasSponsorSig = ctx.tx.isFieldPresent(sfSponsorSignature); + + if ((hasSponsor || hasSponsorFlags || hasSponsorSig) && !ctx.rules.enabled(featureSponsor)) + return temDISABLED; + + if (hasSponsor != hasSponsorFlags) + { + JLOG(ctx.j.debug()) << "preflight1: sponsor and sponsor flags mismatch"; + return temINVALID_FLAG; + } + if (hasSponsorSig && (!hasSponsor || !hasSponsorFlags)) + { + JLOG(ctx.j.debug()) << "preflight1: sponsor signature without sponsor definition"; + return temMALFORMED; + } + + if (hasSponsorFlags) + { + auto const sponsorFlags = ctx.tx.getFieldU32(sfSponsorFlags); + if (((sponsorFlags & spfSponsorFlagMask) != 0u) || sponsorFlags == 0) + { + JLOG(ctx.j.debug()) << "preflight1: invalid sponsor flags"; + return temINVALID_FLAG; + } + + // Reserve sponsorship is only permitted for an explicit allow-list of + // transaction types, for v1. All other tx types reject spfSponsorReserve here. + if (isReserveSponsored(ctx.tx)) + { + if (!isReserveSponsorAllowed(ctx.tx.getTxnType())) + { + JLOG(ctx.j.debug()) + << "preflight1: spfSponsorReserve not allowed for this transaction type"; + return temINVALID_FLAG; + } + } + } + + if (hasSponsor && ctx.tx.getAccountID(sfSponsor) == ctx.tx.getAccountID(sfAccount)) + { + JLOG(ctx.j.debug()) << "preflight1: Sponsor account cannot be the same as the account"; + return temMALFORMED; + } + + return tesSUCCESS; +} + +/** + * Performs early sanity checks on the account and fee fields + */ NotTEC Transactor::preflight1(PreflightContext const& ctx, std::uint32_t flagMask) { @@ -230,10 +289,15 @@ Transactor::preflight1(PreflightContext const& ctx, std::uint32_t flagMask) if (ctx.tx.isFlag(tfInnerBatchTxn) != ctx.parentBatchId.has_value()) return temINVALID_INNER_BATCH; + if (auto const ter = preflight1Sponsor(ctx); !isTesSuccess(ter)) + return ter; + return tesSUCCESS; } -/** Checks whether the signature appears valid */ +/** + * Checks whether the signature appears valid + */ NotTEC Transactor::preflight2(PreflightContext const& ctx) { @@ -343,6 +407,44 @@ Transactor::checkPermission( return tesSUCCESS; } +NotTEC +Transactor::checkSponsor(ReadView const& view, STTx const& tx) +{ + if (!tx.isFieldPresent(sfSponsor)) + return tesSUCCESS; + + // Reserve sponsorship with permissioned delegation is disallowed. + if (tx.isFieldPresent(sfDelegate) && isReserveSponsored(tx)) + return temINVALID; + + if (!view.exists(keylet::account(tx.getAccountID(sfSponsor)))) + return terNO_ACCOUNT; + + // Skip Sponsorship existence checks if the sponsor has signed the transaction - this + // transaction is valid regardless of the Sponsorship object. + // The use of the Sponsorship object is properly handled in + // getFeePayer/checkReserve/increaseOwnerCount/decreaseOwnerCount. + if (tx.isFieldPresent(sfSponsorSignature)) + return tesSUCCESS; + + // If the transaction contains sfDelegate, the Sponsorship object should be + // between the sponsor and the delegate. + auto const sponsorshipSle = + view.read(keylet::sponsorship(tx.getAccountID(sfSponsor), tx.getInitiator())); + + // sponsorship object missing for pre-funded (no co-signing) tx + if (!sponsorshipSle) + return terNO_PERMISSION; + + if (isFeeSponsored(tx) && sponsorshipSle->isFlag(lsfSponsorshipRequireSignForFee)) + return terNO_PERMISSION; + + if (isReserveSponsored(tx) && sponsorshipSle->isFlag(lsfSponsorshipRequireSignForReserve)) + return terNO_PERMISSION; + + return tesSUCCESS; +} + XRPAmount Transactor::calculateBaseFee(ReadView const& view, STTx const& tx) { @@ -351,6 +453,7 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx) // The computation has two parts: // * The base fee, which is the same for most transactions. // * The additional cost of each multisignature on the transaction. + // * The additional cost of each multisignature on the sponsor. XRPAmount const baseFee = view.fees().base; // Each signer adds one more baseFee to the minimum required fee @@ -358,7 +461,15 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx) std::size_t const signerCount = tx.isFieldPresent(sfSigners) ? tx.getFieldArray(sfSigners).size() : 0; - return baseFee + (signerCount * baseFee); + std::size_t sponsorSignerCount = 0; + if (tx.isFieldPresent(sfSponsorSignature)) + { + auto const sponsorObj = tx.getFieldObject(sfSponsorSignature); + if (sponsorObj.isFieldPresent(sfSigners)) + sponsorSignerCount += sponsorObj.getFieldArray(sfSigners).size(); + } + + return baseFee + ((signerCount + sponsorSignerCount) * baseFee); } XRPAmount @@ -437,12 +548,51 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee) if (feePaid == beast::kZero) return tesSUCCESS; - auto const id = ctx.tx.getFeePayer(); - auto const sle = ctx.view.read(keylet::account(id)); - if (!sle) - return terNO_ACCOUNT; + auto const feePayer = getFeePayer(ctx.view, ctx.tx); + auto const payerSle = ctx.view.read(feePayer.keylet); - auto const balance = (*sle)[sfBalance].xrp(); + if (!payerSle) + { + if (feePayer.type == FeePayerType::SponsorPreFunded) + { + // Sanity check: already checked in checkSponsor + return tefINTERNAL; // LCOV_EXCL_LINE + } + + return terNO_ACCOUNT; + } + + XRPAmount maxSpendable = beast::kZero; + + if (feePayer.type == FeePayerType::SponsorPreFunded) + { + if (payerSle->getType() != ltSPONSORSHIP) + return tefINTERNAL; // LCOV_EXCL_LINE + + if (payerSle->isFieldPresent(feePayer.balanceField)) + maxSpendable = payerSle->getFieldAmount(feePayer.balanceField).xrp(); + + if (payerSle->isFieldPresent(sfMaxFee)) + { + auto const cap = payerSle->getFieldAmount(sfMaxFee).xrp(); + maxSpendable = std::min(maxSpendable, cap); + } + } + else + { + if (payerSle->getType() != ltACCOUNT_ROOT) + return tefINTERNAL; // LCOV_EXCL_LINE + + if (feePayer.type == FeePayerType::SponsorCoSigned) + { + auto const sponsorReserve = accountReserve(ctx.view, payerSle, ctx.j); + maxSpendable = payerSle->getFieldAmount(sfBalance).xrp() - sponsorReserve; + } + else + { + maxSpendable = payerSle->getFieldAmount(feePayer.balanceField).xrp(); + } + } // NOTE: Because preclaim evaluates against a static readview, it // does not reflect fee deductions from other transactions paid by @@ -451,12 +601,12 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee) // transactions, this check may pass optimistically. // The fee shortfall will be handled by the Transactor::reset mechanism, // which caps the fee to the remaining actual balance. - if (balance < feePaid) + if (maxSpendable < feePaid) { - JLOG(ctx.j.trace()) << "Insufficient balance:" << " balance=" << to_string(balance) + JLOG(ctx.j.trace()) << "Insufficient balance:" << " balance=" << to_string(maxSpendable) << " paid=" << to_string(feePaid); - if ((balance > beast::kZero) && !ctx.view.open()) + if ((maxSpendable > beast::kZero) && !ctx.view.open()) { // Closed ledger, non-zero balance, less than fee return tecINSUFF_FEE; @@ -473,16 +623,72 @@ Transactor::payFee() { auto const feePaid = ctx_.tx[sfFee].xrp(); - auto const feePayer = ctx_.tx.getFeePayer(); - auto const sle = view().peek(keylet::account(feePayer)); + auto const feePayer = getFeePayer(view(), ctx_.tx); + auto const sle = view().peek(feePayer.keylet); + + JLOG(j_.trace()) << "Fee payer: " + to_string(feePayer.id); + if (!sle) return tefINTERNAL; // LCOV_EXCL_LINE - // Deduct the fee, so it's not available during the transaction. - // Will only write the account back if the transaction succeeds. - sle->setFieldAmount(sfBalance, sle->getFieldAmount(sfBalance) - feePaid); - if (feePayer != accountID_) - view().update(sle); // done in `apply()` for the account + if (feePaid == beast::kZero) + return tesSUCCESS; + + XRPAmount balance = beast::kZero; + if (sle->isFieldPresent(feePayer.balanceField)) + { + balance = sle->getFieldAmount(feePayer.balanceField).xrp(); + } + else if (feePayer.balanceField != sfFeeAmount) + { + return tefINTERNAL; // LCOV_EXCL_LINE + } + + // A co-signed sponsor pays the fee out of its own account balance, but must + // never be charged into its account reserve, and a pre-funded sponsorship's + // fee is capped by sfMaxFee. Mirror the spendable amount computed in + // checkFee() so both limits are enforced on the apply path too. + XRPAmount spendable = balance; + if (feePayer.type == FeePayerType::SponsorCoSigned) + { + auto const sponsorReserve = accountReserve(view(), sle, j_); + // max(balance - reserve, 0) with overflow handling + spendable = balance > sponsorReserve ? balance - sponsorReserve : beast::kZero; + } + else if (feePayer.type == FeePayerType::SponsorPreFunded && sle->isFieldPresent(sfMaxFee)) + { + auto const cap = sle->getFieldAmount(sfMaxFee).xrp(); + spendable = std::min(spendable, cap); + } + + // Only sponsor fee-payers reject here on insufficient funds. For an + // ordinary account, the fee falls through and is capped by reset(), which + // caps to the account's balance. That capping is wrong for sponsors: a + // co-signed sponsor would be charged into its own reserve, and a prefunded + // sponsorship's fee amount should be rejected rather than partially spent. + if (feePaid > spendable && + (feePayer.type == FeePayerType::SponsorPreFunded || + feePayer.type == FeePayerType::SponsorCoSigned)) + { + if ((spendable > beast::kZero) && !view().open()) + return tecINSUFF_FEE; + + return terINSUF_FEE_B; + } + + auto const feeAmountAfter = balance - feePaid; + + if (feeAmountAfter == beast::kZero && feePayer.balanceField == sfFeeAmount) + { + // Because ltSponsorship.sfFeeAmount is soeOptional + sle->makeFieldAbsent(feePayer.balanceField); + } + else + { + sle->setFieldAmount(feePayer.balanceField, feeAmountAfter); + } + + view().update(sle); // VFALCO Should we call view().rawDestroyXRP() here as well? return tesSUCCESS; @@ -656,7 +862,7 @@ Transactor::ticketDelete( } // Update the Ticket owner's reserve. - adjustOwnerCount(view, sleAccount, -1, j); + decreaseOwnerCountForObject(view, sleAccount, sleTicket, 1, j); // Remove Ticket from ledger. view.erase(sleTicket); @@ -750,6 +956,21 @@ Transactor::checkSign( return tesSUCCESS; } + if (sigObject.isFieldPresent(sfSponsorSignature)) + { + // Co-signed sponsorship + + // Sanity check: already checked in preflight1 + if (!sigObject.isFieldPresent(sfSponsor)) + return tefINTERNAL; // LCOV_EXCL_LINE + + auto const sponsorID = sigObject.getAccountID(sfSponsor); + auto const sponsorSignature = sigObject.getFieldObject(sfSponsorSignature); + if (auto const ret = checkSign(view, flags, std::nullopt, sponsorID, sponsorSignature, j); + !isTesSuccess(ret)) + return ret; + } + // If the pk is empty and not simulate or simulate and signers, // then we must be multi-signing. if (sigObject.isFieldPresent(sfSigners)) @@ -1062,10 +1283,11 @@ removeDeletedTrustLines( } } -/** Reset the context, discarding any changes made and adjust the fee. - - @param fee The transaction fee to be charged. - @return A pair containing the transaction result and the actual fee charged. +/** + * Reset the context, discarding any changes made and adjust the fee. + * + * @param fee The transaction fee to be charged. + * @return A pair containing the transaction result and the actual fee charged. */ std::pair Transactor::reset(XRPAmount fee) @@ -1079,11 +1301,38 @@ Transactor::reset(XRPAmount fee) if (!txnAcct) return {tefINTERNAL, beast::kZero}; - auto const payerSle = view().peek(keylet::account(ctx_.tx.getFeePayer())); + auto const feePayer = getFeePayer(view(), ctx_.tx); + auto const payerSle = view().peek(feePayer.keylet); + if (!payerSle) return {tefINTERNAL, beast::kZero}; // LCOV_EXCL_LINE - auto const balance = payerSle->getFieldAmount(sfBalance).xrp(); + XRPAmount balance = beast::kZero; + if (payerSle->isFieldPresent(feePayer.balanceField)) + { + balance = payerSle->getFieldAmount(feePayer.balanceField).xrp(); + } + else if (feePayer.balanceField != sfFeeAmount) + { + return {tefINTERNAL, beast::kZero}; // LCOV_EXCL_LINE + } + + if (feePayer.type == FeePayerType::SponsorPreFunded && payerSle->isFieldPresent(sfMaxFee)) + { + auto const cap = payerSle->getFieldAmount(sfMaxFee).xrp(); + fee = std::min(fee, cap); + } + + // A co-signed sponsor must never be charged into its own account reserve, + // so the fee is capped to the balance above the reserve rather than to the + // full balance. + XRPAmount spendable = balance; + if (feePayer.type == FeePayerType::SponsorCoSigned) + { + auto const sponsorReserve = accountReserve(view(), payerSle, j_); + // max(balance - reserve, 0) with overflow handling + spendable = balance > sponsorReserve ? balance - sponsorReserve : beast::kZero; + } // balance should have already been checked in checkFee / preFlight. XRPL_ASSERT( @@ -1093,8 +1342,8 @@ Transactor::reset(XRPAmount fee) // We retry/reject the transaction if the account balance is zero or // we're applying against an open ledger and the balance is less than // the fee - if (fee > balance) - fee = balance; + if (fee > spendable) + fee = spendable; // Since we reset the context, we need to charge the fee and update // the account's sequence number (or consume the Ticket) again. @@ -1102,7 +1351,17 @@ Transactor::reset(XRPAmount fee) // If for some reason we are unable to consume the ticket or sequence // then the ledger is corrupted. Rather than make things worse we // reject the transaction. - payerSle->setFieldAmount(sfBalance, balance - fee); + auto const feeAmountAfter = balance - fee; + if (feeAmountAfter == beast::kZero && feePayer.balanceField == sfFeeAmount) + { + // Because ltSponsorship.sfFeeAmount is soeOptional + payerSle->makeFieldAbsent(feePayer.balanceField); + } + else + { + payerSle->setFieldAmount(feePayer.balanceField, feeAmountAfter); + } + TER const ter{consumeSeqProxy(txnAcct)}; XRPL_ASSERT(isTesSuccess(ter), "xrpl::Transactor::reset : result is tesSUCCESS"); @@ -1116,6 +1375,48 @@ Transactor::reset(XRPAmount fee) return {ter, fee}; } +FeePayer +Transactor::getFeePayer(ReadView const& view, STTx const& tx) +{ + if (tx.isFieldPresent(sfSponsor) && isFeeSponsored(tx)) + { + auto const sponsorID = tx.getAccountID(sfSponsor); + auto const sponseeID = tx.getInitiator(); + auto const sponsorshipKeylet = keylet::sponsorship(sponsorID, sponseeID); + + // if pre-funded sponsorship exists, prefer it + if (view.exists(sponsorshipKeylet)) + { + // pre funded + return FeePayer{ + .id = sponsorID, + .keylet = sponsorshipKeylet, + .balanceField = sfFeeAmount, + .type = FeePayerType::SponsorPreFunded}; + } + + // Checked in Transactor::checkSponsor + XRPL_ASSERT( + tx.isFieldPresent(sfSponsorSignature), + "xrpl::getFeePayer has sponsor signature without a sponsorship object"); + + // co-signed + return FeePayer{ + .id = sponsorID, + .keylet = keylet::account(sponsorID), + .balanceField = sfBalance, + .type = FeePayerType::SponsorCoSigned}; + } + + AccountID const payerID = tx.getInitiator(); + auto const payerAccountKeylet = keylet::account(payerID); + auto const payerType = + tx.isFieldPresent(sfDelegate) ? FeePayerType::Delegate : FeePayerType::Account; + + return FeePayer{ + .id = payerID, .keylet = payerAccountKeylet, .balanceField = sfBalance, .type = payerType}; +} + // The sole purpose of this function is to provide a convenient, named // location to set a breakpoint, to be used when replaying transactions. void diff --git a/src/libxrpl/tx/applySteps.cpp b/src/libxrpl/tx/applySteps.cpp index caaacfd010..5af4f621a7 100644 --- a/src/libxrpl/tx/applySteps.cpp +++ b/src/libxrpl/tx/applySteps.cpp @@ -181,6 +181,9 @@ invokePreclaim(PreclaimContext const& ctx) if (NotTEC const result = T::checkPriorTxAndLastLedger(ctx)) return result; + if (NotTEC const result = T::checkSponsor(ctx.view, ctx.tx)) + return result; + if (NotTEC const result = Transactor::invokeCheckPermission(ctx.view, ctx.tx)) return result; diff --git a/src/libxrpl/tx/invariants/InvariantCheck.cpp b/src/libxrpl/tx/invariants/InvariantCheck.cpp index 32df44a96b..220a7a8e41 100644 --- a/src/libxrpl/tx/invariants/InvariantCheck.cpp +++ b/src/libxrpl/tx/invariants/InvariantCheck.cpp @@ -148,6 +148,15 @@ XRPNotCreated::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref a if (isXRP((*before)[sfAmount])) drops_ -= (*before)[sfAmount].xrp().drops(); break; + case ltSPONSORSHIP: + if (before->isFieldPresent(sfFeeAmount)) + { + XRPL_ASSERT( + isXRP((*before)[sfFeeAmount]), + "XRPNotCreated::visitEntry : Sponsorship.FeeAmount is XRP"); + drops_ -= (*before)[sfFeeAmount].xrp().drops(); + } + break; default: break; } @@ -168,6 +177,15 @@ XRPNotCreated::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref a if (!isDelete && isXRP((*after)[sfAmount])) drops_ += (*after)[sfAmount].xrp().drops(); break; + case ltSPONSORSHIP: + if (!isDelete && after->isFieldPresent(sfFeeAmount)) + { + XRPL_ASSERT( + isXRP((*after)[sfFeeAmount]), + "XRPNotCreated::visitEntry : Sponsorship.FeeAmount is XRP"); + drops_ += (*after)[sfFeeAmount].xrp().drops(); + } + break; default: break; } @@ -515,6 +533,20 @@ AccountRootsDeletedClean::finalize( if (enforce) return false; } + // An account should not be deleted with sponsorship fields + if (after->isFieldPresent(sfSponsoredOwnerCount) || + after->isFieldPresent(sfSponsoringOwnerCount) || + after->isFieldPresent(sfSponsoringAccountCount) || after->isFieldPresent(sfSponsor)) + { + JLOG(j.fatal()) << "Invariant failed: account deletion left " + "behind a sponsorship field"; + XRPL_ASSERT( + enforce, + "xrpl::AccountRootsDeletedClean::finalize : " + "deleted account has no sponsorship fields"); + if (enforce) + return false; + } // Simple types for (auto const& [keyletfunc, _1, _2] : kDirectAccountKeylets) { @@ -881,8 +913,10 @@ ValidPseudoAccounts::visitEntry(bool isDelete, SLE::const_ref before, SLE::const // 1. Exactly one of the pseudo-account fields is set. // 2. The sequence number is not changed. // 3. The lsfDisableMaster, lsfDefaultRipple, and lsfDepositAuth - // flags are set. + // flags are set. // 4. The RegularKey is not set. + // 5. The SponsoredOwnerCount, SponsoringOwnerCount, SponsoringAccountCount, Sponsor + // fields are not set. { std::vector const& fields = getPseudoAccountFields(); @@ -908,6 +942,12 @@ ValidPseudoAccounts::visitEntry(bool isDelete, SLE::const_ref before, SLE::const { errors_.emplace_back("pseudo-account has a regular key"); } + if (after->isFieldPresent(sfSponsoredOwnerCount) || + after->isFieldPresent(sfSponsoringOwnerCount) || after->isFieldPresent(sfSponsor) || + after->isFieldPresent(sfSponsoringAccountCount)) + { + errors_.emplace_back("pseudo-account has a sponsorship field"); + } } } } diff --git a/src/libxrpl/tx/invariants/SponsorshipInvariant.cpp b/src/libxrpl/tx/invariants/SponsorshipInvariant.cpp new file mode 100644 index 0000000000..bdf37492f3 --- /dev/null +++ b/src/libxrpl/tx/invariants/SponsorshipInvariant.cpp @@ -0,0 +1,158 @@ +#include +// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace xrpl { + +// Add new sponsorship-related invariants implementations +void +SponsorshipOwnerCountsMatch::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) +{ + auto getSponsored = [](SLE::const_ref sle) -> std::uint32_t { + if (sle && sle->getType() == ltACCOUNT_ROOT) + return sle->getFieldU32(sfSponsoredOwnerCount); + return 0; + }; + auto getSponsoring = [](SLE::const_ref sle) -> std::uint32_t { + if (sle && sle->getType() == ltACCOUNT_ROOT) + return sle->getFieldU32(sfSponsoringOwnerCount); + return 0; + }; + + auto getOwnerCount = [](SLE::const_ref sle) -> std::uint32_t { + if (sle && sle->getType() == ltACCOUNT_ROOT) + return sle->getFieldU32(sfOwnerCount); + return 0; + }; + + auto getSponsoredObjectOwnerCount = [&](SLE::const_ref sle) -> std::uint32_t { + if (!sle) + return 0; + switch (sle->getType()) + { + case ltACCOUNT_ROOT: + return 0; + case ltRIPPLE_STATE: { + // A trust line can be reserve-sponsored independently on each + // side, so it may contribute up to two sponsored owner counts. + uint32_t ownerCount = 0; + if (sle->isFieldPresent(sfHighSponsor)) + ownerCount++; + if (sle->isFieldPresent(sfLowSponsor)) + ownerCount++; + return ownerCount; + } + default: + // Every other supported type carries a single sfSponsor field + // and contributes its full owner-count magnitude only when it is + // sponsored. + if (!sle->isFieldPresent(sfSponsor)) + return 0; + return getLedgerEntryOwnerCount(*sle); + } + }; + + // The values are implicitly casted to std::int64_t to calculate deltas. + std::int64_t const beforeSponsored = getSponsored(before); + std::int64_t const afterSponsored = getSponsored(after); + std::int64_t const beforeSponsoring = getSponsoring(before); + std::int64_t const afterSponsoring = getSponsoring(after); + + std::int64_t const beforeSponsoredObjectOwnerCount = getSponsoredObjectOwnerCount(before); + std::int64_t const afterSponsoredObjectOwnerCount = + isDelete ? 0 : getSponsoredObjectOwnerCount(after); + + deltaSponsoredOwnerCount_ += (afterSponsored - beforeSponsored); + deltaSponsoringOwnerCount_ += (afterSponsoring - beforeSponsoring); + + deltaSponsoredObjectOwnerCount_ += + (afterSponsoredObjectOwnerCount - beforeSponsoredObjectOwnerCount); + + if (getOwnerCount(after) < getSponsored(after)) + ownerCountBelowSponsored_ += 1; +} + +bool +SponsorshipOwnerCountsMatch::finalize( + STTx const&, + TER const, + XRPAmount const, + ReadView const&, + beast::Journal const& j) const +{ + if (deltaSponsoredOwnerCount_ != deltaSponsoringOwnerCount_) + { + JLOG(j.fatal()) << "Invariant failed: SponsoredOwnerCount does not " + "equal SponsoringOwnerCount delta."; + return false; + } + + if (ownerCountBelowSponsored_ > 0) + { + JLOG(j.fatal()) + << "Invariant failed: OwnerCount must be greater than or equal to SponsoredOwnerCount."; + return false; + } + + if (deltaSponsoredObjectOwnerCount_ != deltaSponsoredOwnerCount_) + { + JLOG(j.fatal()) << "Invariant failed: SponsoredObjectOwnerCount does not " + "equal SponsoredOwnerCount delta."; + return false; + } + + return true; +} + +void +SponsorshipAccountCountMatchesField::visitEntry(bool, SLE::const_ref before, SLE::const_ref after) +{ + auto getSponsoringAccountCount = [](SLE::const_ref sle) -> std::uint32_t { + if (sle && sle->getType() == ltACCOUNT_ROOT) + return sle->getFieldU32(sfSponsoringAccountCount); + return 0; + }; + + auto hasSponsorField = [](SLE::const_ref sle) -> bool { + return sle && sle->getType() == ltACCOUNT_ROOT && sle->isFieldPresent(sfSponsor); + }; + + std::int64_t const beforeCount = getSponsoringAccountCount(before); + std::int64_t const afterCount = getSponsoringAccountCount(after); + deltaSponsoringAccountCount_ += (afterCount - beforeCount); + + int const beforePresent = hasSponsorField(before) ? 1 : 0; + int const afterPresent = hasSponsorField(after) ? 1 : 0; + deltaSponsorFieldPresence_ += (afterPresent - beforePresent); +} + +bool +SponsorshipAccountCountMatchesField::finalize( + STTx const&, + TER const, + XRPAmount const, + ReadView const&, + beast::Journal const& j) const +{ + if (deltaSponsoringAccountCount_ != deltaSponsorFieldPresence_) + { + JLOG(j.fatal()) << "Invariant failed: Net delta of SponsoringAccountCount does not " + "match net delta of sfSponsor presence."; + return false; + } + + return true; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/invariants/VaultInvariant.cpp b/src/libxrpl/tx/invariants/VaultInvariant.cpp index 84814977db..a9ba0ec874 100644 --- a/src/libxrpl/tx/invariants/VaultInvariant.cpp +++ b/src/libxrpl/tx/invariants/VaultInvariant.cpp @@ -222,7 +222,10 @@ ValidVault::deltaAssetsTxAccount(STTx const& tx, XRPAmount fee) const if (!ret.has_value() || !vaultAsset.native()) return ret; - if (auto const delegate = tx[~sfDelegate]; delegate.has_value() && *delegate != tx[sfAccount]) + // Only add the fee back if tx[sfAccount] actually paid it. When the fee is + // paid by someone else (a delegate or a fee sponsor), the + // account's XRP balance moved only by the vault amount. + if (tx.getFeePayerID() != tx[sfAccount]) return ret; ret->delta += fee.drops(); @@ -1044,10 +1047,8 @@ ValidVault::finalize( case ttLOAN_SET: case ttLOAN_MANAGE: - case ttLOAN_PAY: { - // TBD + case ttLOAN_PAY: return true; - } default: // LCOV_EXCL_START diff --git a/src/libxrpl/tx/paths/BookStep.cpp b/src/libxrpl/tx/paths/BookStep.cpp index 7d1f2edb78..2d9d3b6ece 100644 --- a/src/libxrpl/tx/paths/BookStep.cpp +++ b/src/libxrpl/tx/paths/BookStep.cpp @@ -66,13 +66,14 @@ protected: bool const ownerPaysTransferFee_; // Mark as inactive (dry) if too many offers are consumed bool inactive_ = false; - /** Number of offers consumed or partially consumed the last time - the step ran, including expired and unfunded offers. - - N.B. This is not the total number offers consumed by this step for the - entire payment, it is only the number the last time it ran. Offers may - be partially consumed multiple times during a payment. - */ + /** + * Number of offers consumed or partially consumed the last time + * the step ran, including expired and unfunded offers. + * + * N.B. This is not the total number offers consumed by this step for the + * entire payment, it is only the number the last time it ran. Offers may + * be partially consumed multiple times during a payment. + */ std::uint32_t offersUsed_ = 0; // If set, AMM liquidity might be available // if AMM offer quality is better than CLOB offer @@ -731,7 +732,7 @@ BookStep::forEachOffer( // Create MPToken for the offer's owner. No need to check // for the reserve since the offer is removed if it is consumed. // Therefore, the owner count remains the same. - if (auto const err = checkCreateMPT(sb, assetIn.get(), owner, j_); + if (auto const err = checkCreateMPT(sb, assetIn.get(), owner, {}, j_); !isTesSuccess(err)) { return true; diff --git a/src/libxrpl/tx/paths/MPTEndpointStep.cpp b/src/libxrpl/tx/paths/MPTEndpointStep.cpp index 3b3a65720d..5f97f3ce50 100644 --- a/src/libxrpl/tx/paths/MPTEndpointStep.cpp +++ b/src/libxrpl/tx/paths/MPTEndpointStep.cpp @@ -417,7 +417,8 @@ MPTEndpointOfferCrossingStep::checkCreateMPT(ApplyView& view) // for the reserve since the offer doesn't go on the books // if crossed. Insufficient reserve is allowed if the offer // crossed. See CreateOffer::applyGuts() for reserve check. - if (auto const err = xrpl::checkCreateMPT(view, mptIssue_, dst_, j_); !isTesSuccess(err)) + if (auto const err = xrpl::checkCreateMPT(view, mptIssue_, dst_, {}, j_); + !isTesSuccess(err)) { JLOG(j_.trace()) << "MPTEndpointStep::checkCreateMPT: failed create MPT"; return err; diff --git a/src/libxrpl/tx/transactors/account/AccountDelete.cpp b/src/libxrpl/tx/transactors/account/AccountDelete.cpp index 254733a618..0055fce403 100644 --- a/src/libxrpl/tx/transactors/account/AccountDelete.cpp +++ b/src/libxrpl/tx/transactors/account/AccountDelete.cpp @@ -35,7 +35,6 @@ #include namespace xrpl { - bool AccountDelete::checkExtraFeatures(PreflightContext const& ctx) { @@ -268,6 +267,15 @@ AccountDelete::preclaim(PreclaimContext const& ctx) if (cp) return tecHAS_OBLIGATIONS; + if (sleAccount->isFieldPresent(sfSponsor)) + { + if (dst != sleAccount->getAccountID(sfSponsor)) + return tecNO_SPONSOR_PERMISSION; + } + if (sleAccount->isFieldPresent(sfSponsoringOwnerCount) || + sleAccount->isFieldPresent(sfSponsoringAccountCount)) + return tecHAS_OBLIGATIONS; + // We don't allow an account to be deleted if its sequence number // is within 256 of the current ledger. This prevents replay of old // transactions if this account is resurrected after it is deleted. @@ -394,6 +402,35 @@ AccountDelete::doApply() (*src)[sfBalance] = (*src)[sfBalance] - remainingBalance; ctx_.deliver(remainingBalance); + if (src->isFieldPresent(sfSponsor)) + { + auto const sponsorID = src->getAccountID(sfSponsor); + auto sponsorSle = view().peek(keylet::account(sponsorID)); + + if (!sponsorSle) + return tefINTERNAL; // LCOV_EXCL_LINE + + auto const sponsoringAccountCount = sponsorSle->getFieldU32(sfSponsoringAccountCount); + + XRPL_ASSERT( + sponsoringAccountCount != 0, + "xrpl::AccountDelete::doApply : sponsoring account count is present"); + if (sponsoringAccountCount == 0) + { + // sanity check + // Since sfSponsoringAccountCount is set to soeDEFAULT, the field will not be + // present with a value of 0. + return tefINTERNAL; // LCOV_EXCL_LINE + } + sponsorSle->at(sfSponsoringAccountCount) = sponsoringAccountCount - 1; + view().update(sponsorSle); + + // Following line might look redundant, but without it, sfSponsor + // would end up remaining in after-ltAccountRoot during the + // InvariantCheck. + src->makeFieldAbsent(sfSponsor); + } + XRPL_ASSERT( (*src)[sfBalance] == XRPAmount(0), "xrpl::AccountDelete::doApply : source balance is zero"); diff --git a/src/libxrpl/tx/transactors/account/SignerListSet.cpp b/src/libxrpl/tx/transactors/account/SignerListSet.cpp index 2f95d32664..799c292bfc 100644 --- a/src/libxrpl/tx/transactors/account/SignerListSet.cpp +++ b/src/libxrpl/tx/transactors/account/SignerListSet.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -147,9 +148,7 @@ SignerListSet::preCompute() Transactor::preCompute(); } -// The return type is signed so it is compatible with the 3rd argument -// of adjustOwnerCount() (which must be signed). -static int +static std::uint32_t signerCountBasedOwnerCountDelta(std::size_t entryCount, Rules const& rules) { // We always compute the full change in OwnerCount, taking into account: @@ -165,7 +164,7 @@ signerCountBasedOwnerCountDelta(std::size_t entryCount, Rules const& rules) // units. A SignerList with 8 entries would cost 10 OwnerCount units. // // The static_cast should always be safe since entryCount should always - // be in the range from 1 to 32. + // be in the range from 1 to 32, so the result is always positive. // We've got a lot of room to grow. XRPL_ASSERT( entryCount >= STTx::kMinMultiSigners, @@ -196,12 +195,11 @@ removeSignersFromLedger( // There are two different ways that the OwnerCount could be managed. // If the lsfOneOwnerCount bit is set then remove just one owner count. // Otherwise use the pre-MultiSignReserve amendment calculation. - int removeFromOwnerCount = -1; + std::uint32_t removeFromOwnerCount = 1; if (!signers->isFlag(lsfOneOwnerCount)) { STArray const& actualList = signers->getFieldArray(sfSignerEntries); - removeFromOwnerCount = - signerCountBasedOwnerCountDelta(actualList.size(), view.rules()) * -1; + removeFromOwnerCount = signerCountBasedOwnerCountDelta(actualList.size(), view.rules()); } // Remove the node from the account directory. @@ -215,8 +213,8 @@ removeSignersFromLedger( // LCOV_EXCL_STOP } - adjustOwnerCount( - view, view.peek(accountKeylet), removeFromOwnerCount, registry.getJournal("View")); + decreaseOwnerCountForObject( + view, view.peek(accountKeylet), signers, removeFromOwnerCount, registry.getJournal("View")); view.erase(signers); @@ -315,19 +313,20 @@ SignerListSet::replaceSignerList() if (!sle) return tefINTERNAL; // LCOV_EXCL_LINE - // Compute new reserve. Verify the account has funds to meet the reserve. - std::uint32_t const oldOwnerCount{(*sle)[sfOwnerCount]}; - static constexpr int kAddedOwnerCount = 1; std::uint32_t const flags{lsfOneOwnerCount}; - XRPAmount const newReserve{view().fees().accountReserve(oldOwnerCount + kAddedOwnerCount)}; - // We check the reserve against the starting balance because we want to // allow dipping into the reserve to pay fees. This behavior is consistent // with TicketCreate. - if (preFeeBalance_ < newReserve) - return tecINSUFFICIENT_RESERVE; + if (auto const ret = checkReserve( + ctx_.getApplyViewContext(), + sle, + preFeeBalance_, + {.ownerCountDelta = kAddedOwnerCount}, + ctx_.journal); + !isTesSuccess(ret)) + return ret; // Everything's ducky. Add the ltSIGNER_LIST to the ledger. auto signerList = std::make_shared(signerListKeylet); @@ -349,7 +348,8 @@ SignerListSet::replaceSignerList() // If we succeeded, the new entry counts against the // creator's reserve. - adjustOwnerCount(view(), sle, kAddedOwnerCount, viewJ); + increaseOwnerCount(ctx_.getApplyViewContext(), sle, kAddedOwnerCount, viewJ); + addSponsorToLedgerEntry(ctx_.getApplyViewContext(), signerList); return tesSUCCESS; } diff --git a/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp b/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp index 9092ae4140..e03cb56fd5 100644 --- a/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp +++ b/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp @@ -238,35 +238,35 @@ claimHelper( } /** - Handle a new attestation event. - - Attempt to add the given attestation and reconcile with the current - signer's list. Attestations that are not part of the current signer's - list will be removed. - - @param claimAtt New attestation to add. It will be added if it is not - already part of the collection, or attests to a larger value. - - @param quorum Min weight required for a quorum - - @param signersList Map from signer's account id (derived from public keys) - to the weight of that key. - - @return optional reward accounts. If after handling the new attestation - there is a quorum for the amount specified on the new attestation, then - return the reward accounts for that amount, otherwise return a nullopt. - Note that if the signer's list changes and there have been `commit` - transactions of different amounts then there may be a different subset that - has reached quorum. However, to "trigger" that subset would require adding - (or re-adding) an attestation that supports that subset. - - The reason for using a nullopt instead of an empty vector when a quorum is - not reached is to allow for an interface where a quorum is reached but no - rewards are distributed. - - @note This function is not called `add` because it does more than just - add the new attestation (in fact, it may not add the attestation at - all). Instead, it handles the event of a new attestation. + * Handle a new attestation event. + * + * Attempt to add the given attestation and reconcile with the current + * signer's list. Attestations that are not part of the current signer's + * list will be removed. + * + * @param claimAtt New attestation to add. It will be added if it is not + * already part of the collection, or attests to a larger value. + * + * @param quorum Min weight required for a quorum + * + * @param signersList Map from signer's account id (derived from public keys) + * to the weight of that key. + * + * @return optional reward accounts. If after handling the new attestation + * there is a quorum for the amount specified on the new attestation, then + * return the reward accounts for that amount, otherwise return a nullopt. + * Note that if the signer's list changes and there have been `commit` + * transactions of different amounts then there may be a different subset that + * has reached quorum. However, to "trigger" that subset would require adding + * (or re-adding) an attestation that supports that subset. + * + * The reason for using a nullopt instead of an empty vector when a quorum is + * not reached is to allow for an interface where a quorum is reached but no + * rewards are distributed. + * + * @note This function is not called `add` because it does more than just + * add the new attestation (in fact, it may not add the attestation at + * all). Instead, it handles the event of a new attestation. */ struct OnNewAttestationResult { @@ -364,26 +364,27 @@ struct TransferHelperSubmittingAccountInfo STAmount postFeeBalance; }; -/** Transfer funds from the src account to the dst account - - @param psb The payment sandbox. - @param src The source of funds. - @param dst The destination for funds. - @param dstTag Integer destination tag. Used to check if funds should be - transferred to an account with a `RequireDstTag` flag set. - @param claimOwner Owner of the claim ledger object. - @param amt Amount to transfer from the src account to the dst account. - @param canCreate Flag to determine if accounts may be created using this - transfer. - @param depositAuthPolicy Flag to determine if dst can bypass deposit auth if - it is also the claim owner. - @param submittingAccountInfo If the transaction is allowed to dip into the - reserve to pay fees, then this optional will be seated ("commit" - transactions support this, other transactions should not). - @param j Log - - @return tesSUCCESS if payment succeeds, otherwise the error code for the - failure reason. +/** + * Transfer funds from the src account to the dst account + * + * @param psb The payment sandbox. + * @param src The source of funds. + * @param dst The destination for funds. + * @param dstTag Integer destination tag. Used to check if funds should be + * transferred to an account with a `RequireDstTag` flag set. + * @param claimOwner Owner of the claim ledger object. + * @param amt Amount to transfer from the src account to the dst account. + * @param canCreate Flag to determine if accounts may be created using this + * transfer. + * @param depositAuthPolicy Flag to determine if dst can bypass deposit auth if + * it is also the claim owner. + * @param submittingAccountInfo If the transaction is allowed to dip into the + * reserve to pay fees, then this optional will be seated ("commit" + * transactions support this, other transactions should not). + * @param j Log + * + * @return tesSUCCESS if payment succeeds, otherwise the error code for the + * failure reason. */ TER @@ -435,8 +436,7 @@ transferHelper( return tecINTERNAL; // LCOV_EXCL_LINE { - auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount); - auto const reserve = psb.fees().accountReserve(ownerCount); + auto const reserve = accountReserve(psb, sleSrc, j); auto const availableBalance = [&]() -> STAmount { STAmount curBal = (*sleSrc)[sfBalance]; @@ -505,21 +505,28 @@ transferHelper( return tecXCHAIN_PAYMENT_FAILED; } -/** Action to take when the transfer from the door account to the dst fails - - @note This is useful to prevent a failed "create account" transaction from - blocking subsequent "create account" transactions. -*/ +/** + * Action to take when the transfer from the door account to the dst fails + * + * @note This is useful to prevent a failed "create account" transaction from + * blocking subsequent "create account" transactions. + */ enum class OnTransferFail { - /** Remove the claim even if the transfer fails */ + /** + * Remove the claim even if the transfer fails + */ RemoveClaim, - /** Keep the claim if the transfer fails */ + /** + * Keep the claim if the transfer fails + */ KeepClaim }; struct FinalizeClaimHelperResult { - /// TER for transfering the payment funds + /** + * TER for transfering the payment funds + */ std::optional mainFundsTer; // TER for transfering the reward funds std::optional rewardTer; @@ -563,33 +570,34 @@ struct FinalizeClaimHelperResult } }; -/** Transfer funds from the door account to the dst and distribute rewards - - @param psb The payment sandbox. - @param bridgeSpc Bridge - @param dst The destination for funds. - @param dstTag Integer destination tag. Used to check if funds should be - transferred to an account with a `RequireDstTag` flag set. - @param claimOwner Owner of the claim ledger object. - @param sendingAmount Amount that was committed on the source chain. - @param rewardPoolSrc Source of the funds for the reward pool (claim owner). - @param rewardPool Amount to split among the rewardAccounts. - @param rewardAccounts Account to receive the reward pool. - @param srcChain Chain where the commit event occurred. - @param sleClaimID sle for the claim id (may be NULL or XChainClaimID or - XChainCreateAccountClaimID). Don't read fields that aren't in common - with those two types and always check for NULL. Remove on success (if - not null). Remove on fail if the onTransferFail flag is removeClaim. - @param onTransferFail Flag to determine if the claim is removed on transfer - failure. This is used for create account transactions where claims - are removed so they don't block future txns. - @param j Log - - @return FinalizeClaimHelperResult. See the comments in this struct for what - the fields mean. The individual ters need to be returned instead of - an overall ter because the caller needs this information if the - attestation list changed or not. -*/ +/** + * Transfer funds from the door account to the dst and distribute rewards + * + * @param psb The payment sandbox. + * @param bridgeSpc Bridge + * @param dst The destination for funds. + * @param dstTag Integer destination tag. Used to check if funds should be + * transferred to an account with a `RequireDstTag` flag set. + * @param claimOwner Owner of the claim ledger object. + * @param sendingAmount Amount that was committed on the source chain. + * @param rewardPoolSrc Source of the funds for the reward pool (claim owner). + * @param rewardPool Amount to split among the rewardAccounts. + * @param rewardAccounts Account to receive the reward pool. + * @param srcChain Chain where the commit event occurred. + * @param sleClaimID sle for the claim id (may be NULL or XChainClaimID or + * XChainCreateAccountClaimID). Don't read fields that aren't in common + * with those two types and always check for NULL. Remove on success (if + * not null). Remove on fail if the onTransferFail flag is removeClaim. + * @param onTransferFail Flag to determine if the claim is removed on transfer + * failure. This is used for create account transactions where claims + * are removed so they don't block future txns. + * @param j Log + * + * @return FinalizeClaimHelperResult. See the comments in this struct for what + * the fields mean. The individual ters need to be returned instead of + * an overall ter because the caller needs this information if the + * attestation list changed or not. + */ FinalizeClaimHelperResult finalizeClaimHelper( @@ -726,24 +734,24 @@ finalizeClaimHelper( } // Remove the claim id from the ledger + decreaseOwnerCountForObject(outerSb, sleOwner, sleClaimID, 1, j); outerSb.erase(sleClaimID); - - adjustOwnerCount(outerSb, sleOwner, -1, j); } } return result; } -/** Get signers list corresponding to the account that owns the bridge - - @param view View to read the signer's list from. - @param sleBridge Sle of the bridge. - @param j Log - - @return map of the signer's list (AccountIDs and weights), the quorum, and - error code -*/ +/** + * Get signers list corresponding to the account that owns the bridge + * + * @param view View to read the signer's list from. + * @param sleBridge Sle of the bridge. + * @param j Log + * + * @return map of the signer's list (AccountIDs and weights), the quorum, and + * error code + */ std::tuple, std::uint32_t, TER> getSignersListAndQuorum(ReadView const& view, SLE const& sleBridge, beast::Journal j) { @@ -1028,7 +1036,7 @@ applyCreateAccountAttestations( // Check reserve auto const balance = (*sleDoor)[sfBalance]; - auto const reserve = psb.fees().accountReserve((*sleDoor)[sfOwnerCount] + 1); + auto const reserve = accountReserve(psb, sleDoor, j, {.ownerCountDelta = 1}); if (balance < reserve) return std::unexpected(tecINSUFFICIENT_RESERVE); @@ -1137,7 +1145,7 @@ applyCreateAccountAttestations( return tecINTERNAL; // LCOV_EXCL_LINE // Reserve was already checked - adjustOwnerCount(psb, sleDoor, 1, j); + increaseOwnerCount(psb, sleDoor, {}, 1, j); psb.insert(createdSleClaimID); psb.update(sleDoor); } @@ -1157,7 +1165,11 @@ toClaim(STTx const& tx) try { - STObject o{tx}; + // Copy just the field bag out of the transaction (explicitly, via the + // STObject base) so it can be reinterpreted as a cross-chain attestation + // below, with sfAccount replaced by sfOtherChainSource. STTx-specific + // state (txType_, tid_) is intentionally not needed here. + STObject o{static_cast(tx)}; o.setAccountID(sfAccount, o[sfOtherChainSource]); return TAttestation(o); } @@ -1435,7 +1447,7 @@ XChainCreateBridge::preclaim(PreclaimContext const& ctx) return terNO_ACCOUNT; auto const balance = (*sleAcc)[sfBalance]; - auto const reserve = ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1); + auto const reserve = accountReserve(ctx.view, sleAcc, ctx.j, {.ownerCountDelta = 1}); if (balance < reserve) return tecINSUFFICIENT_RESERVE; @@ -1480,7 +1492,7 @@ XChainCreateBridge::doApply() (*sleBridge)[sfOwnerNode] = *page; } - adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal); + increaseOwnerCount(ctx_.view(), sleAcct, {}, 1, ctx_.journal); ctx_.view().insert(sleBridge); ctx_.view().update(sleAcct); @@ -1983,8 +1995,7 @@ XChainCreateClaimID::preclaim(PreclaimContext const& ctx) return terNO_ACCOUNT; auto const balance = (*sleAcc)[sfBalance]; - auto const reserve = ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1); - + auto const reserve = accountReserve(ctx.view, sleAcc, ctx.j, {.ownerCountDelta = 1}); if (balance < reserve) return tecINSUFFICIENT_RESERVE; } @@ -2042,7 +2053,7 @@ XChainCreateClaimID::doApply() (*sleClaimID)[sfOwnerNode] = *page; } - adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal); + increaseOwnerCount(ctx_.view(), sleAcct, {}, 1, ctx_.journal); ctx_.view().insert(sleClaimID); ctx_.view().update(sleBridge); diff --git a/src/libxrpl/tx/transactors/check/CheckCancel.cpp b/src/libxrpl/tx/transactors/check/CheckCancel.cpp index d966772191..ed60817224 100644 --- a/src/libxrpl/tx/transactors/check/CheckCancel.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCancel.cpp @@ -91,8 +91,7 @@ CheckCancel::doApply() } // If we succeeded, update the check owner's reserve. - auto const sleSrc = view().peek(keylet::account(srcId)); - adjustOwnerCount(view(), sleSrc, -1, viewJ); + decreaseOwnerCountForObject(view(), srcId, sleCheck, 1, viewJ); // Remove check from ledger. view().erase(sleCheck); diff --git a/src/libxrpl/tx/transactors/check/CheckCash.cpp b/src/libxrpl/tx/transactors/check/CheckCash.cpp index 80ef742f52..4e810d94cf 100644 --- a/src/libxrpl/tx/transactors/check/CheckCash.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCash.cpp @@ -3,11 +3,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -30,7 +32,7 @@ #include #include -#include +#include #include namespace xrpl { @@ -175,7 +177,7 @@ CheckCash::preclaim(PreclaimContext const& ctx) // once the check is cashed, since the check's reserve will no // longer be required. So, if we're dealing in XRP, we add one // reserve's worth to the available funds. - if (value.native()) + if (value.native() && !sleCheck->isFieldPresent(sfSponsor)) availableFunds += XRPAmount{ctx.view.fees().increment}; if (value > availableFunds) @@ -307,6 +309,8 @@ CheckCash::doApply() // LCOV_EXCL_STOP } + auto const sponsorCheckSle = getLedgerEntryReserveSponsor(psb, sleCheck); + // Preclaim already checked that source has at least the requested // funds. // @@ -335,7 +339,7 @@ CheckCash::doApply() // from src's directory, we allow them to send that additional // incremental reserve amount in the transfer. Hence the -1 // argument. - STAmount const srcLiquid{xrpLiquid(psb, srcId, -1, viewJ)}; + STAmount const srcLiquid{xrpLiquid(psb, srcId, sponsorCheckSle ? 0 : -1, viewJ)}; // Now, how much do they need in order to be successful? STAmount const xrpDeliver{ @@ -385,14 +389,25 @@ CheckCash::doApply() STAmount const flowDeliver{ optDeliverMin ? maxDeliverMin() : ctx_.tx.getFieldAmount(sfAmount)}; + auto applyViewContext = ApplyViewContext({.view = psb, .tx = ctx_.tx}); + auto const sponsorSle = getTxReserveSponsor(applyViewContext); + if (!sponsorSle) + return sponsorSle.error(); // LCOV_EXCL_LINE + // Check reserve. Return destination account SLE if enough reserve, // otherwise return nullptr. - auto checkReserve = [&]() -> SLE::pointer { + auto checkDstReserve = [&]() -> SLE::pointer { auto sleDst = psb.peek(keylet::account(accountID_)); // Can the account cover the trust line's or MPT reserve? - if (std::uint32_t const ownerCount = {sleDst->at(sfOwnerCount)}; - preFeeBalance_ < psb.fees().accountReserve(ownerCount + 1)) + if (auto const ret = checkReserve( + applyViewContext, + sleDst, + preFeeBalance_, + *sponsorSle, + {.ownerCountDelta = 1}, + j_); + !isTesSuccess(ret)) { JLOG(j_.trace()) << "Trust line does not exist. " "Insufficient reserve to create line."; @@ -426,7 +441,7 @@ CheckCash::doApply() // a. this (destination) account and // b. issuing account (not sending account). - auto const sleDst = checkReserve(); + auto const sleDst = checkDstReserve(); if (sleDst == nullptr) return tecNO_LINE_INSUF_RESERVE; @@ -449,6 +464,7 @@ CheckCash::doApply() Issue(currency, accountID_), // limit of zero 0, // quality in 0, // quality out + *sponsorSle, // sponsor viewJ); // journal !isTesSuccess(ter)) { @@ -491,11 +507,12 @@ CheckCash::doApply() auto const mptokenKey = keylet::mptoken(mptID, accountID_); if (!psb.exists(mptokenKey)) { - auto sleDst = checkReserve(); + auto sleDst = checkDstReserve(); if (sleDst == nullptr) return tecINSUFFICIENT_RESERVE; - if (auto const err = checkCreateMPT(psb, mptID, accountID_, j_); + if (auto const err = + checkCreateMPT(psb, mptID, accountID_, *sponsorSle, j_); !isTesSuccess(err)) { return err; @@ -581,7 +598,7 @@ CheckCash::doApply() } // If we succeeded, update the check owner's reserve. - adjustOwnerCount(psb, psb.peek(keylet::account(srcId)), -1, viewJ); + decreaseOwnerCountForObject(psb, srcId, sleCheck, 1, viewJ); // Remove check from ledger. psb.erase(sleCheck); diff --git a/src/libxrpl/tx/transactors/check/CheckCreate.cpp b/src/libxrpl/tx/transactors/check/CheckCreate.cpp index 595bcc4ab1..cb1d81ba4a 100644 --- a/src/libxrpl/tx/transactors/check/CheckCreate.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCreate.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -194,13 +195,10 @@ CheckCreate::doApply() // A check counts against the reserve of the issuing account, but we // check the starting balance because we want to allow dipping into the // reserve to pay fees. - { - STAmount const reserve{view().fees().accountReserve(sle->getFieldU32(sfOwnerCount) + 1)}; - - if (preFeeBalance_ < reserve) - return tecINSUFFICIENT_RESERVE; - } - + if (auto const ret = checkReserve( + ctx_.getApplyViewContext(), sle, preFeeBalance_, {.ownerCountDelta = 1}, ctx_.journal); + !isTesSuccess(ret)) + return ret; // Note that we use the value from the sequence or ticket as the // Check sequence. For more explanation see comments in SeqProxy.h. std::uint32_t const seq = ctx_.tx.getSeqValue(); @@ -253,7 +251,9 @@ CheckCreate::doApply() sleCheck->setFieldU64(sfOwnerNode, *page); } // If we succeeded, the new entry counts against the creator's reserve. - adjustOwnerCount(view(), sle, 1, viewJ); + + increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, viewJ); + addSponsorToLedgerEntry(ctx_.getApplyViewContext(), sleCheck); return tesSUCCESS; } diff --git a/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp b/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp index e0ebdd893a..c0ef7ea9e8 100644 --- a/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp +++ b/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -11,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -93,12 +93,19 @@ CredentialAccept::doApply() if (!sleSubject || !sleIssuer) return tefINTERNAL; // LCOV_EXCL_LINE - { - STAmount const reserve{ - view().fees().accountReserve(sleSubject->getFieldU32(sfOwnerCount) + 1)}; - if (preFeeBalance_ < reserve) - return tecINSUFFICIENT_RESERVE; - } + auto txSponsorSle = getTxReserveSponsor(ctx_.getApplyViewContext()); + if (!txSponsorSle) + return txSponsorSle.error(); // LCOV_EXCL_LINE + + if (auto const ret = checkReserve( + ctx_.getApplyViewContext(), + sleSubject, + preFeeBalance_, + *txSponsorSle, + {.ownerCountDelta = 1}, + j_); + !isTesSuccess(ret)) + return ret; auto const credType(ctx_.tx[sfCredentialType]); Keylet const credentialKey = keylet::credential(accountID_, issuer, credType); @@ -115,10 +122,16 @@ CredentialAccept::doApply() } sleCred->setFieldU32(sfFlags, lsfAccepted); - view().update(sleCred); - adjustOwnerCount(view(), sleIssuer, -1, j_); - adjustOwnerCount(view(), sleSubject, 1, j_); + // Release the original creation sponsor from the credential (it covered + // the issuer's reserve), then assign the accept tx's sponsor (if any) so + // the credential reflects whoever is now covering the subject's reserve. + decreaseOwnerCountForObject(view(), sleIssuer, sleCred, 1, j_); + removeSponsorFromLedgerEntry(sleCred); + + addSponsorToLedgerEntry(ctx_.getApplyViewContext(), sleCred); + increaseOwnerCount(ctx_.getApplyViewContext(), sleSubject, 1, j_); + view().update(sleCred); return tesSUCCESS; } diff --git a/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp b/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp index acca408a95..e902ee73a6 100644 --- a/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp +++ b/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp @@ -7,13 +7,13 @@ #include #include // IWYU pragma: keep #include +#include #include #include #include #include #include #include -#include #include #include #include @@ -130,12 +130,10 @@ CredentialCreate::doApply() if (!sleIssuer) return tefINTERNAL; // LCOV_EXCL_LINE - { - STAmount const reserve{ - view().fees().accountReserve(sleIssuer->getFieldU32(sfOwnerCount) + 1)}; - if (preFeeBalance_ < reserve) - return tecINSUFFICIENT_RESERVE; - } + if (auto const ret = checkReserve( + ctx_.getApplyViewContext(), sleIssuer, preFeeBalance_, {.ownerCountDelta = 1}, j_); + !isTesSuccess(ret)) + return ret; sleCred->setAccountID(sfSubject, subject); sleCred->setAccountID(sfIssuer, accountID_); @@ -153,7 +151,8 @@ CredentialCreate::doApply() return tecDIR_FULL; sleCred->setFieldU64(sfIssuerNode, *page); - adjustOwnerCount(view(), sleIssuer, 1, j_); + increaseOwnerCount(ctx_.getApplyViewContext(), sleIssuer, 1, j_); + addSponsorToLedgerEntry(ctx_.getApplyViewContext(), sleCred); } if (subject == accountID_) diff --git a/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp b/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp index 32a51555b1..96e6c9e443 100644 --- a/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp +++ b/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp @@ -5,10 +5,10 @@ #include #include #include +#include #include #include #include -#include #include #include #include @@ -98,11 +98,14 @@ DelegateSet::doApply() if (permissions.empty()) return tecINTERNAL; // LCOV_EXCL_LINE - STAmount const reserve{ - ctx_.view().fees().accountReserve(sleOwner->getFieldU32(sfOwnerCount) + 1)}; - - if (preFeeBalance_ < reserve) - return tecINSUFFICIENT_RESERVE; + if (auto const ret = checkReserve( + ctx_.getApplyViewContext(), + sleOwner, + preFeeBalance_, + {.ownerCountDelta = 1}, + ctx_.journal); + !isTesSuccess(ret)) + return ret; sle = std::make_shared(delegateKey); sle->setAccountID(sfAccount, accountID_); @@ -130,7 +133,8 @@ DelegateSet::doApply() (*sle)[sfDestinationNode] = *destPage; ctx_.view().insert(sle); - adjustOwnerCount(ctx_.view(), sleOwner, 1, ctx_.journal); + increaseOwnerCount(ctx_.getApplyViewContext(), sleOwner, 1, ctx_.journal); + addSponsorToLedgerEntry(ctx_.getApplyViewContext(), sle); return tesSUCCESS; } @@ -170,7 +174,7 @@ DelegateSet::deleteDelegate(ApplyView& view, SLE::ref sle, beast::Journal j) if (!sleOwner) return tecINTERNAL; // LCOV_EXCL_LINE - adjustOwnerCount(view, sleOwner, -1, j); + decreaseOwnerCountForObject(view, sleOwner, sle, 1, j); view.erase(sle); diff --git a/src/libxrpl/tx/transactors/dex/AMMCreate.cpp b/src/libxrpl/tx/transactors/dex/AMMCreate.cpp index 66d059c54d..7c7d35497a 100644 --- a/src/libxrpl/tx/transactors/dex/AMMCreate.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMCreate.cpp @@ -332,17 +332,30 @@ applyCreate(ApplyContext& ctx, Sandbox& sb, AccountID const& account, beast::Jou return err; } - if (auto const err = createMPToken(sb, mptID, accountId, flags); !isTesSuccess(err)) + if (auto const err = createMPToken(sb, mptID, accountId, {}, flags); + !isTesSuccess(err)) return err; // Don't adjust AMM owner count. // It's irrelevant for pseudo-account like AMM. return accountSend( - sb, account, accountId, amount, ctx.journal, WaiveTransferFee::Yes); + sb, + account, + accountId, + amount, + ctx.journal, + {}, // don't sponsor for AMM Trustline + WaiveTransferFee::Yes); }, // Set AMM flag on AMM trustline [&](Issue const& issue) -> TER { if (auto const res = accountSend( - sb, account, accountId, amount, ctx.journal, WaiveTransferFee::Yes)) + sb, + account, + accountId, + amount, + ctx.journal, + {}, // don't sponsor for AMM Trustline + WaiveTransferFee::Yes)) return res; // Set AMM flag on AMM trustline if (!isXRP(amount)) diff --git a/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp b/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp index ff7fec019a..0d1798babc 100644 --- a/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp @@ -618,7 +618,13 @@ AMMDeposit::deposit( } auto res = accountSend( - view, accountID_, ammAccount, amountDepositActual, ctx_.journal, WaiveTransferFee::Yes); + view, + accountID_, + ammAccount, + amountDepositActual, + ctx_.journal, + {}, // don't sponsor for AMM Trustline + WaiveTransferFee::Yes); if (!isTesSuccess(res)) { JLOG(ctx_.journal.debug()) << "AMM Deposit: failed to deposit " << amountDepositActual; @@ -642,6 +648,7 @@ AMMDeposit::deposit( ammAccount, *amount2DepositActual, ctx_.journal, + {}, // don't sponsor for AMM Trustline WaiveTransferFee::Yes); if (!isTesSuccess(res)) { @@ -673,7 +680,8 @@ adjustLPTokensOut( return adjustLPTokens(lptAMMBalance, lpTokensDeposit, IsDeposit::Yes); } -/** Proportional deposit of pools assets in exchange for the specified +/** + * Proportional deposit of pools assets in exchange for the specified * amount of LPTokens. */ std::pair @@ -721,7 +729,8 @@ AMMDeposit::equalDepositTokens( } } -/** Proportional deposit of pool assets with the constraints on the maximum +/** + * Proportional deposit of pool assets with the constraints on the maximum * amount of each asset that the trader is willing to deposit. * a = (t/T) * A (1) * b = (t/T) * B (2) @@ -822,7 +831,8 @@ AMMDeposit::equalDepositLimit( return {tecAMM_FAILED, STAmount{}}; } -/** Single asset deposit of the amount of asset specified by Asset1In. +/** + * Single asset deposit of the amount of asset specified by Asset1In. * t = T * (b / B - x) / (1 + x) (3) * where * f1 = (1 - 0.5 * tfee) / (1 - tfee) @@ -870,7 +880,8 @@ AMMDeposit::singleDeposit( tfee); } -/** Single asset asset1 is deposited to obtain some share of +/** + * Single asset asset1 is deposited to obtain some share of * the AMM instance's pools represented by amount of LPTokens. * Use equation 4 to compute the amount of asset1 to be deposited, * given t represented by amount of LPTokens. Equation 4 solves @@ -908,7 +919,8 @@ AMMDeposit::singleDepositTokens( tfee); } -/** Single asset deposit with two constraints. +/** + * Single asset deposit with two constraints. * a. Amount of asset1 if specified (not 0) in Asset1In specifies the maximum * amount of asset1 that the trader is willing to deposit. * b. The effective-price of the LPToken traded out does not exceed diff --git a/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp b/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp index db18d397ef..99dd96b2d1 100644 --- a/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -657,15 +658,15 @@ AMMWithdraw::withdraw( auto sleAccount = view.peek(keylet::account(account)); if (!sleAccount) return tecINTERNAL; // LCOV_EXCL_LINE - STAmount const balance = (*sleAccount)[sfBalance]; - std::uint32_t const ownerCount = sleAccount->at(sfOwnerCount); + auto const balance = (*sleAccount)[sfBalance]->xrp(); // See also TrustSet::doApply() and MPTokenAuthorize::authorize() XRPAmount const reserve( - (ownerCount < 2) ? XRPAmount(beast::kZero) - : view.fees().accountReserve(ownerCount + 1)); + (ownerCount(sleAccount, journal) < 2) + ? XRPAmount(beast::kZero) + : accountReserve(view, sleAccount, journal, {.ownerCountDelta = 1})); - auto const balanceAdj = std::max(priorBalance, balance.xrp()); + auto const balanceAdj = std::max(priorBalance, balance); if (balanceAdj < reserve) return tecINSUFFICIENT_RESERVE; } @@ -682,7 +683,7 @@ AMMWithdraw::withdraw( !isTesSuccess(err)) return err; - if (auto const err = checkCreateMPT(view, mptIssue, account, journal); + if (auto const err = checkCreateMPT(view, mptIssue, account, {}, journal); !isTesSuccess(err)) { return err; @@ -699,7 +700,7 @@ AMMWithdraw::withdraw( // Withdraw amountWithdraw auto res = accountSend( - view, ammAccount, account, amountWithdrawActual, journal, WaiveTransferFee::Yes); + view, ammAccount, account, amountWithdrawActual, journal, {}, WaiveTransferFee::Yes); if (!isTesSuccess(res)) { // LCOV_EXCL_START @@ -718,7 +719,7 @@ AMMWithdraw::withdraw( return {res, STAmount{}, STAmount{}, STAmount{}}; res = accountSend( - view, ammAccount, account, *amount2WithdrawActual, journal, WaiveTransferFee::Yes); + view, ammAccount, account, *amount2WithdrawActual, journal, {}, WaiveTransferFee::Yes); if (!isTesSuccess(res)) { // LCOV_EXCL_START @@ -758,7 +759,8 @@ adjustLPTokensIn( return adjustLPTokens(lptAMMBalance, lpTokensWithdraw, IsDeposit::No); } -/** Proportional withdrawal of pool assets for the amount of LPTokens. +/** + * Proportional withdrawal of pool assets for the amount of LPTokens. */ std::pair AMMWithdraw::equalWithdrawTokens( @@ -822,7 +824,8 @@ AMMWithdraw::deleteAMMAccountIfEmpty( return {ter, true}; } -/** Proportional withdrawal of pool assets for the amount of LPTokens. +/** + * Proportional withdrawal of pool assets for the amount of LPTokens. */ std::tuple> AMMWithdraw::equalWithdrawTokens( @@ -908,7 +911,8 @@ AMMWithdraw::equalWithdrawTokens( // LCOV_EXCL_STOP } -/** All assets withdrawal with the constraints on the maximum amount +/** + * All assets withdrawal with the constraints on the maximum amount * of each asset that the trader is willing to withdraw. * a = (t/T) * A (5) * b = (t/T) * B (6) @@ -998,7 +1002,8 @@ AMMWithdraw::equalWithdrawLimit( tfee); } -/** Withdraw single asset equivalent to the amount specified in Asset1Out. +/** + * Withdraw single asset equivalent to the amount specified in Asset1Out. * t = T * (c - sqrt(c**2 - 4*R))/2 * where R = b/B, c = R*fee + 2 - fee * Use equation 7 to compute the t, given the amount in Asset1Out. @@ -1044,7 +1049,8 @@ AMMWithdraw::singleWithdraw( tfee); } -/** withdrawal of single asset specified in Asset1Out proportional +/** + * withdrawal of single asset specified in Asset1Out proportional * to the share represented by the amount of LPTokens. * Use equation 8 to compute the amount of asset1, given the redeemed t * represented by LPTokens. Let this be Y. @@ -1088,7 +1094,8 @@ AMMWithdraw::singleWithdrawTokens( return {tecAMM_FAILED, STAmount{}}; } -/** Withdraw single asset with two constraints. +/** + * Withdraw single asset with two constraints. * a. amount of asset1 if specified (not 0) in Asset1Out specifies the minimum * amount of asset1 that the trader is willing to withdraw. * b. The effective price of asset traded out does not exceed the amount diff --git a/src/libxrpl/tx/transactors/dex/OfferCreate.cpp b/src/libxrpl/tx/transactors/dex/OfferCreate.cpp index 85face7309..3ff9f2ab15 100644 --- a/src/libxrpl/tx/transactors/dex/OfferCreate.cpp +++ b/src/libxrpl/tx/transactors/dex/OfferCreate.cpp @@ -854,9 +854,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel) return {tefINTERNAL, false}; { - XRPAmount const reserve = - sb.fees().accountReserve(sleCreator->getFieldU32(sfOwnerCount) + 1); - + XRPAmount const reserve = accountReserve(sb, sleCreator, viewJ, {.ownerCountDelta = 1}); if (preFeeBalance_ < reserve) { // If we are here, the signing account had an insufficient reserve @@ -890,7 +888,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel) } // Update owner count. - adjustOwnerCount(sb, sleCreator, 1, viewJ); + increaseOwnerCount(sb, sleCreator, {}, 1, viewJ); JLOG(j_.trace()) << "adding to book: " << to_string(saTakerPays.asset()) << " : " << to_string(saTakerGets.asset()) diff --git a/src/libxrpl/tx/transactors/did/DIDDelete.cpp b/src/libxrpl/tx/transactors/did/DIDDelete.cpp index 90aa21d8a1..a2af4c1100 100644 --- a/src/libxrpl/tx/transactors/did/DIDDelete.cpp +++ b/src/libxrpl/tx/transactors/did/DIDDelete.cpp @@ -50,8 +50,7 @@ DIDDelete::deleteSLE(ApplyView& view, SLE::pointer sle, AccountID const owner, b if (!sleOwner) return tecINTERNAL; // LCOV_EXCL_LINE - adjustOwnerCount(view, sleOwner, -1, j); - view.update(sleOwner); + decreaseOwnerCountForObject(view, sleOwner, sle, 1, j); // Remove object from ledger view.erase(sle); diff --git a/src/libxrpl/tx/transactors/did/DIDSet.cpp b/src/libxrpl/tx/transactors/did/DIDSet.cpp index 1392581bb0..2659460b8d 100644 --- a/src/libxrpl/tx/transactors/did/DIDSet.cpp +++ b/src/libxrpl/tx/transactors/did/DIDSet.cpp @@ -72,7 +72,8 @@ addSLE(ApplyContext& ctx, SLE::ref sle, AccountID const& owner) // Check reserve availability for new object creation { auto const balance = STAmount((*sleAccount)[sfBalance]).xrp(); - auto const reserve = ctx.view().fees().accountReserve((*sleAccount)[sfOwnerCount] + 1); + auto const reserve = + accountReserve(ctx.view(), sleAccount, ctx.journal, {.ownerCountDelta = 1}); if (balance < reserve) return tecINSUFFICIENT_RESERVE; @@ -89,7 +90,7 @@ addSLE(ApplyContext& ctx, SLE::ref sle, AccountID const& owner) return tecDIR_FULL; // LCOV_EXCL_LINE (*sle)[sfOwnerNode] = *page; } - adjustOwnerCount(ctx.view(), sleAccount, 1, ctx.journal); + increaseOwnerCount(ctx.view(), sleAccount, {}, 1, ctx.journal); ctx.view().update(sleAccount); return tesSUCCESS; diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp index 55a9133e1e..feed43d410 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp @@ -181,7 +181,7 @@ EscrowCancel::doApply() if (auto const ret = std::visit( [&](T const&) { return escrowUnlockApplyHelper( - ctx_.view(), + ctx_.getApplyViewContext(), kParityRate, ctx_.view().rules().enabled(fixCleanup3_2_0) ? sle : slep, preFeeBalance_, @@ -209,8 +209,7 @@ EscrowCancel::doApply() } } - adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal); - ctx_.view().update(sle); + decreaseOwnerCountForObject(ctx_.view(), sle, slep, 1, ctx_.journal); // Remove escrow from ledger ctx_.view().erase(slep); diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp index 531098bd1c..50f2e8b859 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -434,16 +435,33 @@ EscrowCreate::doApply() // Check reserve and funds availability STAmount const amount{ctx_.tx[sfAmount]}; - auto const reserve = ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + 1); - auto const balance = sle->getFieldAmount(sfBalance).xrp(); - if (balance < reserve) - return tecINSUFFICIENT_RESERVE; + // First check: whoever is on the hook for the new owner increment + // can cover it. When sponsored this hits the sponsor branch and + // validates the sponsor's reserve + remaining credit. When + // unsponsored this hits the source branch and validates the + // source's pre-lock balance against base + (currentOC+1)*increment. + if (auto const ret = + checkReserve(ctx_.getApplyViewContext(), sle, balance, {.ownerCountDelta = 1}, j_); + !isTesSuccess(ret)) + return ret; - // Check reserve and funds availability if (isXRP(amount)) { - if (balance < reserve + STAmount(amount).xrp()) + // Second check (XRP escrow only): after locking the escrowed + // amount, the source must still meet its own reserve floor. This is + // always the source's own balance against the source's own reserve — + // the sponsor's reserve was already validated above, and a sponsor + // never covers the locked funds. We compare directly (rather than via + // checkReserve) because that helper diverts to the sponsor's balance + // when a sponsor is present and would ignore the source's post-lock + // balance entirely. ownerCountDelta differs by case: + // - sponsored: 0 — sponsor covers the new owner increment, so the + // source only owes reserve for its current owners. + // - unsponsored: 1 — source owes reserve including the new increment. + auto const sourceReserve = accountReserve( + ctx_.view(), sle, j_, {.ownerCountDelta = getTxReserveSponsorID(ctx_.tx) ? 0 : 1}); + if (balance - STAmount(amount).xrp() < sourceReserve) return tecUNFUNDED; } @@ -535,7 +553,8 @@ EscrowCreate::doApply() } // increment owner count - adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal); + increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, ctx_.journal); + addSponsorToLedgerEntry(ctx_.getApplyViewContext(), slep); ctx_.view().update(sle); return tesSUCCESS; } diff --git a/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp b/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp index 7f1f9ec078..8bc98c7aa8 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp @@ -340,6 +340,16 @@ EscrowFinish::doApply() } } + // With the Sponsor amendment, release the escrow reserve before delivery. + // Token delivery can auto-create a destination holding, and the same + // sponsor (or the same account, for a self-escrow) may cover both the + // escrow being removed and the holding being created. Without the + // amendment, keep the legacy order: releasing early changes the reserve + // arithmetic for self-escrows and would break consensus if not gated. + bool const sponsorEnabled = ctx_.view().rules().enabled(featureSponsor); + if (sponsorEnabled) + decreaseOwnerCountForObject(ctx_.view(), account, slep, 1, ctx_.journal); + STAmount const amount = slep->getFieldAmount(sfAmount); // Transfer amount to destination if (isXRP(amount)) @@ -359,7 +369,7 @@ EscrowFinish::doApply() if (auto const ret = std::visit( [&](T const&) { return escrowUnlockApplyHelper( - ctx_.view(), + ctx_.getApplyViewContext(), lockedRate, sled, preFeeBalance_, @@ -389,10 +399,9 @@ EscrowFinish::doApply() ctx_.view().update(sled); - // Adjust source owner count - auto const sle = ctx_.view().peek(keylet::account(account)); - adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal); - ctx_.view().update(sle); + // Adjust source owner count (legacy position, pre-Sponsor) + if (!sponsorEnabled) + decreaseOwnerCountForObject(ctx_.view(), account, slep, 1, ctx_.journal); // Remove escrow from ledger ctx_.view().erase(slep); diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp index 9bb84e878e..b914f3cf24 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp @@ -372,7 +372,7 @@ LoanBrokerCoverClawback::doApply() associateAsset(*sleBroker, vaultAsset); // Transfer assets from pseudo-account to depositor. - return accountSend(view(), brokerPseudoID, account, clawAmount, j_, WaiveTransferFee::Yes); + return accountSend(view(), brokerPseudoID, account, clawAmount, j_, {}, WaiveTransferFee::Yes); } void diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp index 69b28b57af..09ab03347a 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp @@ -176,7 +176,7 @@ LoanBrokerCoverDeposit::doApply() // Transfer assets from depositor to pseudo-account. if (auto ter = - accountSend(view(), accountID_, brokerPseudoID, amount, j_, WaiveTransferFee::Yes)) + accountSend(view(), accountID_, brokerPseudoID, amount, j_, {}, WaiveTransferFee::Yes)) return ter; // Increase the LoanBroker's CoverAvailable by Amount diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp index 008857a4ad..498f3c99eb 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp @@ -211,7 +211,14 @@ LoanBrokerCoverWithdraw::doApply() associateAsset(*broker, vaultAsset); - return doWithdraw(view(), tx, accountID_, dstAcct, brokerPseudoID, preFeeBalance_, amount, j_); + return doWithdraw( + ctx_.getApplyViewContext(), + accountID_, + dstAcct, + brokerPseudoID, + preFeeBalance_, + amount, + j_); } void diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp index 141cc2cf56..b36977d225 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp @@ -155,11 +155,11 @@ LoanBrokerDelete::doApply() { auto const coverAvailable = STAmount{vaultAsset, broker->at(sfCoverAvailable)}; if (auto const ter = accountSend( - view(), brokerPseudoID, accountID_, coverAvailable, j_, WaiveTransferFee::Yes)) + view(), brokerPseudoID, accountID_, coverAvailable, j_, {}, WaiveTransferFee::Yes)) return ter; } - if (auto ter = removeEmptyHolding(view(), brokerPseudoID, vaultAsset, j_)) + if (auto ter = removeEmptyHolding(ctx_.getApplyViewContext(), brokerPseudoID, vaultAsset, j_)) return ter; auto brokerPseudoSLE = view().peek(keylet::account(brokerPseudoID)); @@ -186,8 +186,6 @@ LoanBrokerDelete::doApply() view().erase(brokerPseudoSLE); - view().erase(broker); - { auto owner = view().peek(keylet::account(accountID_)); if (!owner) @@ -195,9 +193,11 @@ LoanBrokerDelete::doApply() // Decreases the owner count by two: one for the LoanBroker object, and // one for the pseudo-account. - adjustOwnerCount(view(), owner, -2, j_); + decreaseOwnerCountForObject(view(), owner, broker, 2, j_); } + view().erase(broker); + associateAsset(*broker, vaultAsset); return tesSUCCESS; diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp index ab1c8f22dc..e9c153404c 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp @@ -238,9 +238,8 @@ LoanBrokerSet::doApply() // Increases the owner count by two: one for the LoanBroker object, and // one for the pseudo-account. - adjustOwnerCount(view, owner, 2, j_); - auto const ownerCount = owner->at(sfOwnerCount); - if (preFeeBalance_ < view.fees().accountReserve(ownerCount)) + increaseOwnerCount(view, owner, {}, 2, j_); + if (preFeeBalance_ < accountReserve(view, owner, j_)) return tecINSUFFICIENT_RESERVE; auto maybePseudo = createPseudoAccount(view, broker->key(), sfLoanBrokerID); @@ -249,7 +248,8 @@ LoanBrokerSet::doApply() auto& pseudo = *maybePseudo; auto pseudoId = pseudo->at(sfAccount); - if (auto ter = addEmptyHolding(view, pseudoId, preFeeBalance_, sleVault->at(sfAsset), j_)) + if (auto ter = addEmptyHolding( + ctx_.getApplyViewContext(), pseudoId, preFeeBalance_, sleVault->at(sfAsset), j_)) return ter; // Initialize data fields: diff --git a/src/libxrpl/tx/transactors/lending/LoanDelete.cpp b/src/libxrpl/tx/transactors/lending/LoanDelete.cpp index 3459119379..1a77489b4b 100644 --- a/src/libxrpl/tx/transactors/lending/LoanDelete.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanDelete.cpp @@ -107,9 +107,8 @@ LoanDelete::doApply() view.erase(loanSle); // Decrement the LoanBroker's owner count. - // The broker's owner count is solely for the number of outstanding loans, - // and is distinct from the broker's pseudo-account's owner count - adjustOwnerCount(view, brokerSle, -1, j_); + adjustLoanBrokerOwnerCount(view, brokerSle, -1, j_); + // If there are no loans left, then any remaining debt must be forgiven, // because there is no other way to pay it back. if (brokerSle->at(sfOwnerCount) == 0) @@ -129,7 +128,7 @@ LoanDelete::doApply() } } // Decrement the borrower's owner count - adjustOwnerCount(view, borrowerSle, -1, j_); + decreaseOwnerCountForObject(view, borrowerSle, loanSle, 1, j_); // These associations shouldn't do anything, but do them just to be safe associateAsset(*loanSle, vaultAsset); diff --git a/src/libxrpl/tx/transactors/lending/LoanManage.cpp b/src/libxrpl/tx/transactors/lending/LoanManage.cpp index 830eb30272..a0aa948876 100644 --- a/src/libxrpl/tx/transactors/lending/LoanManage.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanManage.cpp @@ -292,6 +292,7 @@ LoanManage::defaultLoan( vaultSle->at(sfAccount), STAmount{vaultAsset, defaultCovered}, j, + {}, WaiveTransferFee::Yes); } diff --git a/src/libxrpl/tx/transactors/lending/LoanPay.cpp b/src/libxrpl/tx/transactors/lending/LoanPay.cpp index fcda2a9fff..54ee85b186 100644 --- a/src/libxrpl/tx/transactors/lending/LoanPay.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanPay.cpp @@ -625,7 +625,11 @@ LoanPay::doApply() { // The broker may have deleted their holding. Recreate it if needed if (auto const ter = addEmptyHolding( - view, brokerPayee, brokerPayeeSle->at(sfBalance).value().xrp(), asset, j_); + ctx_.getApplyViewContext(), + brokerPayee, + brokerPayeeSle->at(sfBalance).value().xrp(), + asset, + j_); ter && ter != tecDUPLICATE) { // ignore tecDUPLICATE. That means the holding already exists, diff --git a/src/libxrpl/tx/transactors/lending/LoanSet.cpp b/src/libxrpl/tx/transactors/lending/LoanSet.cpp index 1ac387d1b1..694d01c69f 100644 --- a/src/libxrpl/tx/transactors/lending/LoanSet.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanSet.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -56,6 +57,12 @@ LoanSet::preflight(PreflightContext const& ctx) auto const& tx = ctx.tx; + if (tx.isFieldPresent(sfSponsorFlags) && isReserveSponsored(tx)) + { + JLOG(ctx.j.debug()) << "LoanSet: reserve sponsorship is not allowed."; + return temINVALID_FLAG; + } + // Special case for Batch inner transactions if (tx.isFlag(tfInnerBatchTxn) && ctx.rules.enabled(featureBatchV1_1) && !tx.isFieldPresent(sfCounterparty)) @@ -512,12 +519,12 @@ LoanSet::doApply() } } - adjustOwnerCount(view, borrowerSle, 1, j_); + increaseOwnerCount(view, borrowerSle, {}, 1, j_); + { - auto const ownerCount = borrowerSle->at(sfOwnerCount); auto const balance = accountID_ == borrower ? preFeeBalance_ : borrowerSle->at(sfBalance).value().xrp(); - if (balance < view.fees().accountReserve(ownerCount)) + if (balance < accountReserve(view, borrowerSle, j_)) return tecINSUFFICIENT_RESERVE; } @@ -531,8 +538,9 @@ LoanSet::doApply() borrower == accountID_ || borrower == counterparty, "xrpl::LoanSet::doApply", "borrower signed transaction"); + auto applyViewContext = ctx_.getApplyViewContext(); if (auto const ter = addEmptyHolding( - view, borrower, borrowerSle->at(sfBalance).value().xrp(), vaultAsset, j_); + applyViewContext, borrower, borrowerSle->at(sfBalance).value().xrp(), vaultAsset, j_); ter && ter != tecDUPLICATE) { // ignore tecDUPLICATE. That means the holding already exists, and @@ -555,7 +563,11 @@ LoanSet::doApply() "broker owner signed transaction"); if (auto const ter = addEmptyHolding( - view, brokerOwner, brokerOwnerSle->at(sfBalance).value().xrp(), vaultAsset, j_); + applyViewContext, + brokerOwner, + brokerOwnerSle->at(sfBalance).value().xrp(), + vaultAsset, + j_); ter && ter != tecDUPLICATE) { // ignore tecDUPLICATE. That means the holding already exists, @@ -631,9 +643,7 @@ LoanSet::doApply() // Update the balances in the loan broker adjustImpreciseNumber(brokerSle->at(sfDebtTotal), newDebtDelta, vaultAsset, vaultScale); - // The broker's owner count is solely for the number of outstanding loans, - // and is distinct from the broker's pseudo-account's owner count - adjustOwnerCount(view, brokerSle, 1, j_); + adjustLoanBrokerOwnerCount(view, brokerSle, 1, j_); loanSequenceProxy += 1; // The sequence should be extremely unlikely to roll over, but fail if it // does diff --git a/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp b/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp index 14bf3646c0..41bb051768 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -387,8 +388,7 @@ NFTokenAcceptOffer::transferNFToken( auto const buyerOwnerCountAfter = sleBuyer->getFieldU32(sfOwnerCount); if (buyerOwnerCountAfter > buyerOwnerCountBefore) { - if (auto const reserve = view().fees().accountReserve(buyerOwnerCountAfter); - buyerBalance < reserve) + if (buyerBalance < accountReserve(view(), sleBuyer, j_)) return tecINSUFFICIENT_RESERVE; } diff --git a/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp b/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp index d8e5a7b235..5490c2de3a 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -331,12 +332,14 @@ NFTokenMint::doApply() // allows NFTs to be added to the page (and burn fees) without // requiring the reserve to be met each time. The reserve is // only managed when a new NFT page or sell offer is added. - if (auto const ownerCountAfter = - view().read(keylet::account(accountID_))->getFieldU32(sfOwnerCount); + auto const sleAccount = view().read(keylet::account(accountID_)); + if (!sleAccount) + return tefINTERNAL; // LCOV_EXCL_LINE + + if (auto const ownerCountAfter = sleAccount->getFieldU32(sfOwnerCount); ownerCountAfter > ownerCountBefore) { - if (auto const reserve = view().fees().accountReserve(ownerCountAfter); - preFeeBalance_ < reserve) + if (preFeeBalance_ < accountReserve(view(), sleAccount, j_)) return tecINSUFFICIENT_RESERVE; } return tesSUCCESS; diff --git a/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp b/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp index 9a26816155..b4c12b9514 100644 --- a/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp +++ b/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -13,6 +14,8 @@ #include #include +#include + namespace xrpl { NotTEC @@ -68,10 +71,8 @@ OracleDelete::deleteOracle( if (!sleOwner) return tecINTERNAL; // LCOV_EXCL_LINE - auto const count = sle->getFieldArray(sfPriceDataSeries).size() > 5 ? -2 : -1; - - adjustOwnerCount(view, sleOwner, count, j); - + std::uint32_t const count = calculateOracleReserve(sle); + decreaseOwnerCountForObject(view, sleOwner, sle, count, j); view.erase(sle); return tesSUCCESS; diff --git a/src/libxrpl/tx/transactors/oracle/OracleSet.cpp b/src/libxrpl/tx/transactors/oracle/OracleSet.cpp index 12d826e54d..ead26824e5 100644 --- a/src/libxrpl/tx/transactors/oracle/OracleSet.cpp +++ b/src/libxrpl/tx/transactors/oracle/OracleSet.cpp @@ -1,9 +1,11 @@ #include #include +#include #include #include #include +#include #include #include #include @@ -22,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -150,8 +153,9 @@ OracleSet::preclaim(PreclaimContext const& ctx) if (!pairsDel.empty()) return tecTOKEN_PAIR_NOT_FOUND; - auto const oldCount = sle->getFieldArray(sfPriceDataSeries).size() > 5 ? 2 : 1; - auto const newCount = pairs.size() > 5 ? 2 : 1; + auto const oldCount = calculateOracleReserve(sle); + auto const newCount = calculateOracleReserve(pairs); + adjustReserve = newCount - oldCount; } else @@ -160,7 +164,7 @@ OracleSet::preclaim(PreclaimContext const& ctx) if (!ctx.tx.isFieldPresent(sfProvider) || !ctx.tx.isFieldPresent(sfAssetClass)) return temMALFORMED; - adjustReserve = pairs.size() > 5 ? 2 : 1; + adjustReserve = calculateOracleReserve(pairs); } if (pairs.empty()) @@ -169,7 +173,7 @@ OracleSet::preclaim(PreclaimContext const& ctx) return tecARRAY_TOO_LARGE; auto const reserve = - ctx.view.fees().accountReserve(sleSetter->getFieldU32(sfOwnerCount) + adjustReserve); + accountReserve(ctx.view, sleSetter, ctx.j, {.ownerCountDelta = adjustReserve}); auto const& balance = sleSetter->getFieldAmount(sfBalance); if (balance < reserve) @@ -179,11 +183,22 @@ OracleSet::preclaim(PreclaimContext const& ctx) } static bool -adjustOwnerCount(ApplyContext& ctx, int count) +adjustOracleOwnerCount(ApplyContext& ctx, int count) { + XRPL_ASSERT(std::abs(count) <= 2, "xrpl::adjustOracleOwnerCount abs(counter) <= 2"); + if (auto const sleAccount = ctx.view().peek(keylet::account(ctx.tx[sfAccount]))) { - adjustOwnerCount(ctx.view(), sleAccount, count, ctx.journal); + if (count > 0) + { + increaseOwnerCount( + ctx.view(), sleAccount, {}, static_cast(count), ctx.journal); + } + else if (count < 0) + { + decreaseOwnerCount( + ctx.view(), sleAccount, {}, static_cast(-count), ctx.journal); + } return true; } @@ -228,7 +243,7 @@ OracleSet::doApply() priceData.setFieldCurrency(sfQuoteAsset, entry.getFieldCurrency(sfQuoteAsset)); pairs.emplace(tokenPairKey(entry), std::move(priceData)); } - auto const oldCount = pairs.size() > 5 ? 2 : 1; + auto const oldCount = calculateOracleReserve(pairs); // update/add/delete pairs for (auto const& entry : ctx_.tx.getFieldArray(sfPriceDataSeries)) { @@ -266,9 +281,10 @@ OracleSet::doApply() (*sle)[sfOracleDocumentID] = ctx_.tx[sfOracleDocumentID]; } - auto const newCount = pairs.size() > 5 ? 2 : 1; - auto const adjust = newCount - oldCount; - if (adjust != 0 && !adjustOwnerCount(ctx_, adjust)) + auto const newCount = calculateOracleReserve(pairs); + int32_t const adjust = newCount - oldCount; + + if (adjust != 0 && !adjustOracleOwnerCount(ctx_, adjust)) return tefINTERNAL; // LCOV_EXCL_LINE ctx_.view().update(sle); @@ -317,8 +333,8 @@ OracleSet::doApply() (*sle)[sfOwnerNode] = *page; - auto const count = series.size() > 5 ? 2 : 1; - if (!adjustOwnerCount(ctx_, count)) + auto const count = calculateOracleReserve(series); + if (!adjustOracleOwnerCount(ctx_, count)) return tefINTERNAL; // LCOV_EXCL_LINE ctx_.view().insert(sle); diff --git a/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp b/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp index 7e950dc743..d3e2af86ef 100644 --- a/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp +++ b/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp @@ -7,13 +7,13 @@ #include #include #include +#include #include #include #include #include #include #include -#include #include #include #include @@ -151,6 +151,7 @@ DepositPreauth::preclaim(PreclaimContext const& ctx) TER DepositPreauth::doApply() { + auto applyViewContext = ctx_.getApplyViewContext(); if (ctx_.tx.isFieldPresent(sfAuthorize)) { auto const sleOwner = view().peek(keylet::account(accountID_)); @@ -160,13 +161,10 @@ DepositPreauth::doApply() // A preauth counts against the reserve of the issuing account, but we // check the starting balance because we want to allow dipping into the // reserve to pay fees. - { - STAmount const reserve{ - view().fees().accountReserve(sleOwner->getFieldU32(sfOwnerCount) + 1)}; - - if (preFeeBalance_ < reserve) - return tecINSUFFICIENT_RESERVE; - } + if (auto const ret = checkReserve( + applyViewContext, sleOwner, preFeeBalance_, {.ownerCountDelta = 1}, j_); + !isTesSuccess(ret)) + return ret; // Preclaim already verified that the Preauth entry does not yet exist. // Create and populate the Preauth entry. @@ -190,7 +188,8 @@ DepositPreauth::doApply() slePreauth->setFieldU64(sfOwnerNode, *page); // If we succeeded, the new entry counts against the creator's reserve. - adjustOwnerCount(view(), sleOwner, 1, j_); + increaseOwnerCount(applyViewContext, sleOwner, 1, j_); + addSponsorToLedgerEntry(applyViewContext, slePreauth); } else if (ctx_.tx.isFieldPresent(sfUnauthorize)) { @@ -207,13 +206,10 @@ DepositPreauth::doApply() // A preauth counts against the reserve of the issuing account, but we // check the starting balance because we want to allow dipping into the // reserve to pay fees. - { - STAmount const reserve{ - view().fees().accountReserve(sleOwner->getFieldU32(sfOwnerCount) + 1)}; - - if (preFeeBalance_ < reserve) - return tecINSUFFICIENT_RESERVE; - } + if (auto const ret = checkReserve( + applyViewContext, sleOwner, preFeeBalance_, {.ownerCountDelta = 1}, j_); + !isTesSuccess(ret)) + return ret; // Preclaim already verified that the Preauth entry does not yet exist. // Create and populate the Preauth entry. @@ -251,7 +247,8 @@ DepositPreauth::doApply() slePreauth->setFieldU64(sfOwnerNode, *page); // If we succeeded, the new entry counts against the creator's reserve. - adjustOwnerCount(view(), sleOwner, 1, j_); + increaseOwnerCount(applyViewContext, sleOwner, 1, j_); + addSponsorToLedgerEntry(applyViewContext, slePreauth); } else if (ctx_.tx.isFieldPresent(sfUnauthorizeCredentials)) { @@ -289,8 +286,7 @@ DepositPreauth::removeFromLedger(ApplyView& view, uint256 const& preauthIndex, b if (!sleOwner) return tefINTERNAL; // LCOV_EXCL_LINE - adjustOwnerCount(view, sleOwner, -1, j); - + decreaseOwnerCountForObject(view, sleOwner, slePreauth, 1, j); // Remove DepositPreauth from ledger. view.erase(slePreauth); diff --git a/src/libxrpl/tx/transactors/payment/Payment.cpp b/src/libxrpl/tx/transactors/payment/Payment.cpp index 6e3883572a..17c96a1919 100644 --- a/src/libxrpl/tx/transactors/payment/Payment.cpp +++ b/src/libxrpl/tx/transactors/payment/Payment.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,7 @@ #include #include +#include #include #include #include @@ -120,6 +122,21 @@ Payment::preflight(PreflightContext const& ctx) if (!ctx.rules.enabled(featureMPTokensV1) && isDstMPT) return temDISABLED; + if (tx.isFlag(tfSponsorCreatedAccount)) + { + if (!ctx.rules.enabled(featureSponsor)) + return temDISABLED; + + if (tx.isFlag(tfNoRippleDirect) || tx.isFlag(tfPartialPayment) || tx.isFlag(tfLimitQuality)) + return temINVALID_FLAG; + + if (tx.isFieldPresent(sfSendMax) || tx.isFieldPresent(sfPaths)) + return temINVALID; + + if (!dstAmount.native()) + return temBAD_AMOUNT; + } + if (!mpTokensV2 && isDstMPT && ctx.tx.isFieldPresent(sfPaths)) return temMALFORMED; @@ -383,15 +400,28 @@ Payment::preclaim(PreclaimContext const& ctx) { // accountReserve is the minimum amount that an account can have. // Reserve is not scaled by load. - JLOG(ctx.j.trace()) << "Delay transaction: Destination account does not exist. " - << "Insufficent payment to create account."; + if (!ctx.tx.isFlag(tfSponsorCreatedAccount)) + { + // The minimum amount when creating a Sponsored Account is 1 drop. + // Since the reserve is covered by the sponsor, you don't need to hold the + // 1-increment reserve yourself. + JLOG(ctx.j.trace()) << "Delay transaction: Destination account does not exist. " + << "Insufficient payment to create account."; - // TODO: de-dupe - // Another transaction could create the account and then this - // transaction would succeed. - return tecNO_DST_INSUF_XRP; + // TODO: de-dupe + // Another transaction could create the account and then this + // transaction would succeed. + return tecNO_DST_INSUF_XRP; + } } } + else if (ctx.tx.isFlag(tfSponsorCreatedAccount)) + { + // The tfSponsorCreatedAccount flag is specific to account creation via + // sponsorship. If the destination account already exists, applying this + // flag is invalid. + return tecNO_SPONSOR_PERMISSION; + } else if (sleDst->isFlag(lsfRequireDestTag) && !ctx.tx.isFieldPresent(sfDestinationTag)) { // The tag is basically account-specific information we don't @@ -470,6 +500,27 @@ Payment::doApply() sleDst->setFieldU32(sfSequence, view().seq()); sleDst->setFieldAmount(sfBalance, XRPAmount(beast::kZero)); + if (ctx_.tx.isFlag(tfSponsorCreatedAccount)) + { + auto const sponsor = view().peek(keylet::account(accountID_)); + if (!sponsor) + return tefINTERNAL; // LCOV_EXCL_LINE + auto const currentSponsoringAccountCount = + sponsor->getFieldU32(sfSponsoringAccountCount); + if (currentSponsoringAccountCount == std::numeric_limits::max()) + { + // LCOV_EXCL_START + JLOG(j_.fatal()) << "Sponsoring account count overflow for account " + << to_string(accountID_); + return tecINTERNAL; + // LCOV_EXCL_STOP + } + sponsor->setFieldU32(sfSponsoringAccountCount, currentSponsoringAccountCount + 1); + + addSponsorToLedgerEntry(sleDst, sponsor); + view().update(sponsor); + } + view().insert(sleDst); } else @@ -636,16 +687,12 @@ Payment::doApply() if (!sleSrc) return tefINTERNAL; // LCOV_EXCL_LINE - // ownerCount is the number of entries in this ledger for this - // account that require a reserve. - auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount); + // the number of reserves in this ledger for this account that require a + // reserve. + auto const reserve = accountReserve(view(), sleSrc, j_); - // This is the total reserve in drops. - auto const reserve = view().fees().accountReserve(ownerCount); - - // In a delegated payment, the fee payer is the delegated account, - // not the source account (accountID_). - bool const accountIsPayer = (ctx_.tx.getFeePayer() == accountID_); + // In a delegated / fee sponsored payment, the fee payer is not the source account (accountID_). + bool const accountIsPayer = ctx_.tx.getFeePayerID() == accountID_; // preFeeBalance_ is the balance on the source account (accountID_) BEFORE the fees // were charged. If source account is the fee payer, it must also cover the fee. diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp index 977da56861..b17430948a 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp +++ b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -77,9 +78,10 @@ PaymentChannelCreate::preclaim(PreclaimContext const& ctx) return terNO_ACCOUNT; // Check reserve and funds availability + if (!ctx.view.rules().enabled(featureSponsor)) { auto const balance = (*sle)[sfBalance]; - auto const reserve = ctx.view.fees().accountReserve((*sle)[sfOwnerCount] + 1); + auto const reserve = ctx.view.fees().accountReserve((*sle)[sfOwnerCount] + 1, 1); if (balance < reserve) return tecINSUFFICIENT_RESERVE; @@ -131,6 +133,36 @@ PaymentChannelCreate::doApply() return tecEXPIRED; } + if (ctx_.view().rules().enabled(featureSponsor)) + { + // First check: whoever is on the hook for the new owner increment + // can cover it. When sponsored this hits the sponsor branch and + // validates the sponsor's reserve + remaining credit. When + // unsponsored this hits the source branch and validates the + // source's pre-lock balance against base + (currentOC+1)*increment. + if (auto const ret = checkReserve( + ctx_.getApplyViewContext(), sle, preFeeBalance_, {.ownerCountDelta = 1}, j_); + !isTesSuccess(ret)) + return ret; + + // Second check: after locking sfAmount in the channel, the source + // must still meet its own reserve floor. This is always the + // source's own balance against the source's own reserve — the + // sponsor's reserve was already validated above, and a sponsor + // never covers the locked funds. We compare directly (rather than + // via checkReserve) because that helper diverts to the + // sponsor's balance when a sponsor is present and would ignore the + // source's post-lock balance entirely. ownerCountDelta differs by + // case: + // - sponsored: 0 — sponsor covers the new owner increment, so + // the source only owes reserve for its current owners. + // - unsponsored: 1 — source owes reserve including the new increment. + auto const sourceReserve = accountReserve( + ctx_.view(), sle, j_, {.ownerCountDelta = getTxReserveSponsorID(ctx_.tx) ? 0 : 1}); + if (preFeeBalance_ - ctx_.tx[sfAmount].xrp() < sourceReserve) + return tecUNFUNDED; + } + auto const dst = ctx_.tx[sfDestination]; // Create PayChan in ledger. @@ -178,7 +210,8 @@ PaymentChannelCreate::doApply() // Deduct owner's balance, increment owner count (*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount]; - adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal); + increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, ctx_.journal); + addSponsorToLedgerEntry(ctx_.getApplyViewContext(), slep); ctx_.view().update(sle); return tesSUCCESS; diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp index 4e3c5dd638..1d1cf07e25 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp +++ b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -87,13 +88,18 @@ PaymentChannelFund::doApply() { // Check reserve and funds availability - auto const balance = (*sle)[sfBalance]; - auto const reserve = ctx_.view().fees().accountReserve((*sle)[sfOwnerCount]); + STAmount const balance = (*sle)[sfBalance]; + if (auto const ret = checkReserve(ctx_.getApplyViewContext(), sle, balance.xrp(), {}, j_); + !isTesSuccess(ret)) + return ret; - if (balance < reserve) - return tecINSUFFICIENT_RESERVE; - - if (balance < reserve + ctx_.tx[sfAmount]) + // After locking sfAmount in the channel, the source must still meet + // its own reserve floor. We compare directly (rather than via + // checkReserve) because that helper diverts to the sponsor's balance + // when a sponsor is present and would ignore the source's post-lock + // balance entirely. Funding an existing channel adds no owned object, + // so there is no owner-count delta. + if (balance < accountReserve(ctx_.view(), sle, j_) + ctx_.tx[sfAmount]) return tecUNFUNDED; } diff --git a/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp index 7eb3f282b9..dc1b323482 100644 --- a/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp +++ b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp @@ -42,7 +42,9 @@ PermissionedDomainDelete::preclaim(PreclaimContext const& ctx) return tesSUCCESS; } -/** Attempt to delete the Permissioned Domain. */ +/** + * Attempt to delete the Permissioned Domain. + */ TER PermissionedDomainDelete::doApply() { @@ -65,7 +67,7 @@ PermissionedDomainDelete::doApply() XRPL_ASSERT( ownerSle && ownerSle->getFieldU32(sfOwnerCount) > 0, "xrpl::PermissionedDomainDelete::doApply : nonzero owner count"); - adjustOwnerCount(view(), ownerSle, -1, ctx_.journal); + decreaseOwnerCountForObject(view(), ownerSle, slePd, 1, ctx_.journal); view().erase(slePd); return tesSUCCESS; diff --git a/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp index 0e71ceada1..61ebdcf9c7 100644 --- a/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp +++ b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp @@ -73,7 +73,9 @@ PermissionedDomainSet::preclaim(PreclaimContext const& ctx) return tesSUCCESS; } -/** Attempt to create the Permissioned Domain. */ +/** + * Attempt to create the Permissioned Domain. + */ TER PermissionedDomainSet::doApply() { @@ -106,7 +108,8 @@ PermissionedDomainSet::doApply() // Create new permissioned domain. // Check reserve availability for new object creation auto const balance = STAmount((*ownerSle)[sfBalance]).xrp(); - auto const reserve = ctx_.view().fees().accountReserve((*ownerSle)[sfOwnerCount] + 1); + auto const reserve = + accountReserve(ctx_.view(), ownerSle, ctx_.journal, {.ownerCountDelta = 1}); if (balance < reserve) return tecINSUFFICIENT_RESERVE; @@ -125,7 +128,7 @@ PermissionedDomainSet::doApply() slePd->setFieldU64(sfOwnerNode, *page); // If we succeeded, the new entry counts against the creator's reserve. - adjustOwnerCount(view(), ownerSle, 1, ctx_.journal); + increaseOwnerCount(view(), ownerSle, {}, 1, ctx_.journal); view().insert(slePd); } diff --git a/src/libxrpl/tx/transactors/sponsor/SponsorshipSet.cpp b/src/libxrpl/tx/transactors/sponsor/SponsorshipSet.cpp new file mode 100644 index 0000000000..2b6ab8cf15 --- /dev/null +++ b/src/libxrpl/tx/transactors/sponsor/SponsorshipSet.cpp @@ -0,0 +1,420 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace xrpl { + +static bool +hasSponsorshipBudget( + SLE::const_ref sponsorshipSle, + std::optional const& feeAmount, + std::optional const& remainingOwnerCount) +{ + // A field the transaction omits keeps whatever the existing object holds, + // so fall back to the current SLE value when the tx does not set it. + bool const hasFeeAmount = feeAmount + ? *feeAmount > beast::kZero + : sponsorshipSle && (*sponsorshipSle)[~sfFeeAmount].value_or(STAmount{0}) > beast::kZero; + + bool const hasRemainingOwnerCount = remainingOwnerCount + ? *remainingOwnerCount > 0 + : sponsorshipSle && (*sponsorshipSle)[~sfRemainingOwnerCount].value_or(0) > 0; + + return hasFeeAmount || hasRemainingOwnerCount; +} + +TxConsequences +SponsorshipSet::makeTxConsequences(PreflightContext const& ctx) +{ + auto const feeAmount = ctx.tx[~sfFeeAmount]; + return TxConsequences{ctx.tx, feeAmount.has_value() ? feeAmount->xrp() : beast::kZero}; +} + +std::uint32_t +SponsorshipSet::getFlagsMask(PreflightContext const& ctx) +{ + return tfSponsorshipSetMask; +} + +NotTEC +SponsorshipSet::preflight(PreflightContext const& ctx) +{ + if (ctx.tx.isFlag(tfSponsorshipSetRequireSignForFee) && + ctx.tx.isFlag(tfSponsorshipClearRequireSignForFee)) + return temINVALID_FLAG; + if (ctx.tx.isFlag(tfSponsorshipSetRequireSignForReserve) && + ctx.tx.isFlag(tfSponsorshipClearRequireSignForReserve)) + return temINVALID_FLAG; + + auto const account = ctx.tx.getAccountID(sfAccount); + bool const hasSponsor = ctx.tx.isFieldPresent(sfCounterpartySponsor); + bool const hasSponsee = ctx.tx.isFieldPresent(sfSponsee); + + // The transaction must specify either Sponsor or Sponsee, but not both. + if (hasSponsor == hasSponsee) + return temMALFORMED; + + auto const sponsorID = ctx.tx[~sfCounterpartySponsor].value_or(account); + auto const sponseeID = ctx.tx[~sfSponsee].value_or(account); + + if (sponsorID == sponseeID) + return temMALFORMED; + + if (ctx.tx.isFlag(tfDeleteObject)) + { + // Transactions deleting `Sponsorship` cannot set modification flags. + constexpr std::uint32_t kModifyFlags = tfSponsorshipSetRequireSignForFee | + tfSponsorshipSetRequireSignForReserve | tfSponsorshipClearRequireSignForFee | + tfSponsorshipClearRequireSignForReserve; + + if ((ctx.tx.getFlags() & kModifyFlags) != 0u) + return temINVALID_FLAG; + + // Transactions deleting `Sponsorship` cannot include modification fields. + if (ctx.tx.isFieldPresent(sfFeeAmount) || ctx.tx.isFieldPresent(sfRemainingOwnerCount) || + ctx.tx.isFieldPresent(sfMaxFee)) + return temMALFORMED; + } + else + { + // Both sponsor and sponsee can delete a Sponsorship object, but only + // the sponsor can create or update one. + if (account != sponsorID) + return temMALFORMED; + + // FeeAmount and MaxFee must be non-negative XRP amounts when present. + auto const checkOptionalAmountField = [&](SField const& field) -> NotTEC { + if (!ctx.tx.isFieldPresent(field)) + return tesSUCCESS; + + auto const amount = ctx.tx.getFieldAmount(field); + + if (!isXRP(amount)) + return temBAD_AMOUNT; + + if (amount.xrp() < beast::kZero) + return temBAD_AMOUNT; + + return tesSUCCESS; + }; + + if (auto const ret = checkOptionalAmountField(sfFeeAmount); !isTesSuccess(ret)) + return ret; + + if (auto const ret = checkOptionalAmountField(sfMaxFee); !isTesSuccess(ret)) + return ret; + } + + return tesSUCCESS; +} + +TER +SponsorshipSet::preclaim(PreclaimContext const& ctx) +{ + auto const sponsorID = ctx.tx[~sfCounterpartySponsor].value_or(ctx.tx[sfAccount]); + auto const sponseeID = ctx.tx[~sfSponsee].value_or(ctx.tx[sfAccount]); + + if (sponseeID == sponsorID) + return tecINTERNAL; // LCOV_EXCL_LINE + + auto const sponsorAccSle = ctx.view.read(keylet::account(sponsorID)); + if (!sponsorAccSle) + return tecNO_DST; + + auto const sponseeSle = ctx.view.read(keylet::account(sponseeID)); + if (!sponseeSle) + return tecNO_DST; + + // Pseudo-accounts cannot participate in sponsorship. + if (isPseudoAccount(sponsorAccSle) || isPseudoAccount(sponseeSle)) + return tecNO_PERMISSION; + + auto const sponsorshipSle = ctx.view.read(keylet::sponsorship(sponsorID, sponseeID)); + + // Deleting a Sponsorship object requires the object to already exist. + if (ctx.tx.isFlag(tfDeleteObject) && !sponsorshipSle) + return tecNO_ENTRY; + + // Reject creating or updating a Sponsorship that would be left with no + // budget (neither a positive FeeAmount nor a positive RemainingOwnerCount). + // Such an object is unusable yet still consumes the sponsor's reserve. + if (!ctx.tx.isFlag(tfDeleteObject) && + !hasSponsorshipBudget(sponsorshipSle, ctx.tx[~sfFeeAmount], ctx.tx[~sfRemainingOwnerCount])) + return tecNO_PERMISSION; + + return tesSUCCESS; +} + +static TER +deleteSponsorship(ApplyView& view, SLE::ref sle, beast::Journal j) +{ + if (!sle) + return tecINTERNAL; // LCOV_EXCL_LINE + + auto const sponsorID = (*sle)[sfOwner]; + auto const sponseeID = (*sle)[sfSponsee]; + + // The sponsor owns the Sponsorship object, so deletion releases the + // sponsor's owner reserve. + auto sponsorAccSle = view.peek(keylet::account(sponsorID)); + if (!sponsorAccSle) + return tecINTERNAL; // LCOV_EXCL_LINE + + if (!view.dirRemove(keylet::ownerDir(sponsorID), (*sle)[sfOwnerNode], sle->key(), false)) + { + // LCOV_EXCL_START + JLOG(j.fatal()) << "Unable to delete Sponsorship from sponsor."; + return tefBAD_LEDGER; + // LCOV_EXCL_STOP + } + if (!view.dirRemove(keylet::ownerDir(sponseeID), (*sle)[sfSponseeNode], sle->key(), false)) + { + // LCOV_EXCL_START + JLOG(j.fatal()) << "Unable to delete Sponsorship from sponsee."; + return tefBAD_LEDGER; + // LCOV_EXCL_STOP + } + + decreaseOwnerCountForObject(view, sponsorAccSle, sle, 1, j); + + // Return any prefunded fee amount to the sponsor before erasing the object. + if (sle->isFieldPresent(sfFeeAmount)) + { + (*sponsorAccSle)[sfBalance] += sle->getFieldAmount(sfFeeAmount); + view.update(sponsorAccSle); + } + + view.erase(sle); + + return tesSUCCESS; +} + +TER +SponsorshipSet::doApply() +{ + auto const sponsorID = ctx_.tx[~sfCounterpartySponsor].value_or(accountID_); + auto const sponseeID = ctx_.tx[~sfSponsee].value_or(accountID_); + + if (sponseeID == sponsorID) + return tecINTERNAL; // LCOV_EXCL_LINE + + auto const sponsorAccSle = ctx_.view().peek(keylet::account(sponsorID)); + if (!sponsorAccSle) + return tecINTERNAL; // LCOV_EXCL_LINE + + if (!ctx_.view().exists(keylet::account(sponseeID))) + return tecINTERNAL; // LCOV_EXCL_LINE + + auto const sponsorKeylet = keylet::sponsorship(sponsorID, sponseeID); + auto const sponsorshipSle = ctx_.view().peek(sponsorKeylet); + + if (ctx_.tx.isFlag(tfDeleteObject)) + { + if (!sponsorshipSle) + return tecINTERNAL; // LCOV_EXCL_LINE + + return deleteSponsorship(ctx_.view(), sponsorshipSle, ctx_.journal); + } + + auto const feeAmount = ctx_.tx[~sfFeeAmount]; + auto const maxFee = ctx_.tx[~sfMaxFee]; + auto const remainingOwnerCount = ctx_.tx[~sfRemainingOwnerCount]; + + bool const hasPositiveFeeAmount = feeAmount.has_value() && *feeAmount > beast::kZero; + + auto reserveSponsorAccSle = getTxReserveSponsor(ctx_.getApplyViewContext()); + if (!reserveSponsorAccSle) + return reserveSponsorAccSle.error(); // LCOV_EXCL_LINE + + if (!sponsorshipSle) + { + // Create a new Sponsorship object between the sponsor and sponsee. + auto newSle = std::make_shared(sponsorKeylet); + + (*newSle)[sfOwner] = sponsorID; + (*newSle)[sfSponsee] = sponseeID; + if (feeAmount && (*feeAmount).xrp() > (*sponsorAccSle)[sfBalance]) + return tecUNFUNDED; + + STAmount sponsorBalanceAfterFee = (*sponsorAccSle)[sfBalance]; + if (hasPositiveFeeAmount) + sponsorBalanceAfterFee -= *feeAmount; + + if (auto const ret = checkReserve( + ctx_.getApplyViewContext(), + sponsorAccSle, + sponsorBalanceAfterFee.xrp(), + *reserveSponsorAccSle, + {.ownerCountDelta = 1}, + ctx_.journal, + tecUNFUNDED); + !isTesSuccess(ret)) + { + return ret; + } + + if (hasPositiveFeeAmount) + { + // New object: FeeAmount starts absent, so deduct and record the full amount + (*newSle)[sfFeeAmount] = *feeAmount; + (*sponsorAccSle)[sfBalance] -= *feeAmount; + } + + if (maxFee && *maxFee > beast::kZero) + (*newSle)[sfMaxFee] = *maxFee; + if (remainingOwnerCount && *remainingOwnerCount > 0) + (*newSle)[sfRemainingOwnerCount] = *remainingOwnerCount; + + std::uint32_t flags = 0; + if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForFee)) + flags |= lsfSponsorshipRequireSignForFee; + + if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForReserve)) + flags |= lsfSponsorshipRequireSignForReserve; + + (*newSle)[sfFlags] = flags; + + auto const sponsorPage = view().dirInsert( + keylet::ownerDir(sponsorID), sponsorKeylet, describeOwnerDir(sponsorID)); + if (!sponsorPage) + return tecDIR_FULL; // LCOV_EXCL_LINE + (*newSle)[sfOwnerNode] = *sponsorPage; + + auto const sponseePage = view().dirInsert( + keylet::ownerDir(sponseeID), sponsorKeylet, describeOwnerDir(sponseeID)); + if (!sponseePage) + return tecDIR_FULL; // LCOV_EXCL_LINE + (*newSle)[sfSponseeNode] = *sponseePage; + + // NOLINTNEXTLINE(readability-suspicious-call-argument) + increaseOwnerCount(view(), sponsorAccSle, *reserveSponsorAccSle, 1, ctx_.journal); + addSponsorToLedgerEntry(newSle, *reserveSponsorAccSle); + + ctx_.view().insert(newSle); + return tesSUCCESS; + } + + // Update the existing Sponsorship object. + if (feeAmount) + { + auto const currentFeeAmount = (*sponsorshipSle)[~sfFeeAmount].valueOr(XRPAmount{0}); + auto const feeAmountDelta = XRPAmount(*feeAmount - currentFeeAmount); + + if (feeAmountDelta > beast::kZero && feeAmountDelta > (*sponsorAccSle)[sfBalance]) + return tecUNFUNDED; + + // Move the FeeAmount delta between the sponsor balance and Sponsorship + // object. + if (feeAmountDelta != beast::kZero) + { + STAmount sponsorBalanceAfterFee = (*sponsorAccSle)[sfBalance]; + sponsorBalanceAfterFee -= feeAmountDelta; + + if (auto const ret = checkReserve( + ctx_.getApplyViewContext(), + sponsorAccSle, + sponsorBalanceAfterFee.xrp(), + *reserveSponsorAccSle, + {}, + ctx_.journal, + tecUNFUNDED); + !isTesSuccess(ret)) + { + return ret; + } + + (*sponsorAccSle)[sfBalance] -= feeAmountDelta; + if (*feeAmount == beast::kZero) + { + (*sponsorshipSle).makeFieldAbsent(sfFeeAmount); + } + else + { + (*sponsorshipSle).setFieldAmount(sfFeeAmount, *feeAmount); + } + ctx_.view().update(sponsorAccSle); + } + } + + if (maxFee) + { + if (*maxFee == beast::kZero) + { + (*sponsorshipSle).makeFieldAbsent(sfMaxFee); + } + else + { + (*sponsorshipSle)[sfMaxFee] = *maxFee; + } + } + + if (remainingOwnerCount) + { + if (*remainingOwnerCount == 0) + { + sponsorshipSle->makeFieldAbsent(sfRemainingOwnerCount); + } + else + { + sponsorshipSle->at(sfRemainingOwnerCount) = *remainingOwnerCount; + } + } + + // Apply requested flag changes. + auto flags = sponsorshipSle->getFieldU32(sfFlags); + if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForFee)) + flags |= lsfSponsorshipRequireSignForFee; + + if (ctx_.tx.isFlag(tfSponsorshipClearRequireSignForFee)) + flags &= ~lsfSponsorshipRequireSignForFee; + + if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForReserve)) + flags |= lsfSponsorshipRequireSignForReserve; + + if (ctx_.tx.isFlag(tfSponsorshipClearRequireSignForReserve)) + flags &= ~lsfSponsorshipRequireSignForReserve; + + if (flags != (*sponsorshipSle)[sfFlags]) + (*sponsorshipSle)[sfFlags] = flags; + + view().update(sponsorshipSle); + + return tesSUCCESS; +} + +void +SponsorshipSet::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) +{ +} + +bool +SponsorshipSet::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/transactors/sponsor/SponsorshipTransfer.cpp b/src/libxrpl/tx/transactors/sponsor/SponsorshipTransfer.cpp new file mode 100644 index 0000000000..0e036649fd --- /dev/null +++ b/src/libxrpl/tx/transactors/sponsor/SponsorshipTransfer.cpp @@ -0,0 +1,542 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace xrpl { + +// Increment an uint32 sponsor count field and update the SLE. +static TER +incrementSponsorCount( + ApplyView& view, + SLE::ref sle, + SF_UINT32 const& field, + std::uint32_t const delta) +{ + auto const currentValue = sle->getFieldU32(field); + if (std::numeric_limits::max() - currentValue < delta) + { + // LCOV_EXCL_START + UNREACHABLE("xrpl::incrementSponsorCount : sponsor field overflow"); + return tecINTERNAL; + // LCOV_EXCL_STOP + } + + sle->at(field) = currentValue + delta; + view.update(sle); + return tesSUCCESS; +} + +// Decrement an uint32 sponsor count field and update the SLE. +static TER +decrementSponsorCount( + ApplyView& view, + SLE::ref sle, + SF_UINT32 const& field, + std::uint32_t const delta) +{ + auto const currentValue = sle->getFieldU32(field); + if (currentValue < delta) + { + // LCOV_EXCL_START + UNREACHABLE("xrpl::decrementSponsorCount : sponsor field underflow"); + return tecINTERNAL; + // LCOV_EXCL_STOP + } + + sle->at(field) = currentValue - delta; + view.update(sle); + return tesSUCCESS; +} + +// Consume the sponsor's pre-funded reserve budget and lowers the Sponsorship +// object's RemainingOwnerCount. +static TER +decrementPrefundedReserveCount(ApplyView& view, SLE::ref sponsorshipSle, std::uint32_t const delta) +{ + if (delta == 0) + return tesSUCCESS; // LCOV_EXCL_LINE + + auto const currentReserveCount = sponsorshipSle->getFieldU32(sfRemainingOwnerCount); + if (currentReserveCount < delta) + { + // LCOV_EXCL_START + // Already verified by checkReserve (sufficient RemainingOwnerCount) + UNREACHABLE("xrpl::decrementPrefundedReserveCount : invalid reserve count"); + return tefINTERNAL; + // LCOV_EXCL_STOP + } + + sponsorshipSle->at(sfRemainingOwnerCount) = currentReserveCount - delta; + view.update(sponsorshipSle); + return tesSUCCESS; +} + +std::uint32_t +SponsorshipTransfer::getFlagsMask(PreflightContext const& ctx) +{ + return tfSponsorshipTransferMask; +} + +NotTEC +SponsorshipTransfer::preflight(PreflightContext const& ctx) +{ + static constexpr auto transferFlags = + tfSponsorshipCreate | tfSponsorshipReassign | tfSponsorshipEnd; + if (std::popcount(ctx.tx.getFlags() & transferFlags) != 1) + { + JLOG(ctx.j.debug()) << "preflight: Only one SponsorshipTransfer flag can be set per tx."; + return temINVALID_FLAG; + } + + if (ctx.tx.isFlag(tfSponsorshipCreate)) + { + // Creating sponsorship transfers an unsponsored target from the sponsee + // to a reserve sponsor identified by sfSponsor + spfSponsorReserve. + if (!ctx.tx.isFieldPresent(sfSponsor)) + { + JLOG(ctx.j.debug()) << "preflight: sfSponsor must be present when creating sponsorship"; + return temMALFORMED; + } + + if (!isReserveSponsored(ctx.tx)) + { + JLOG(ctx.j.debug()) + << "preflight: spfSponsorReserve must be set when creating sponsorship"; + return temINVALID_FLAG; + } + + if (ctx.tx.isFieldPresent(sfSponsee)) + { + JLOG(ctx.j.debug()) + << "preflight: sfSponsee must not be present when creating sponsorship"; + return temMALFORMED; + } + } + + if (ctx.tx.isFlag(tfSponsorshipReassign)) + { + // Reassigning sponsorship transfers an already sponsored target from its + // current reserve sponsor to the new sponsor identified by sfSponsor + + // spfSponsorReserve. + if (!ctx.tx.isFieldPresent(sfSponsor)) + { + JLOG(ctx.j.debug()) + << "preflight: sfSponsor must be present when reassigning sponsorship"; + return temMALFORMED; + } + + if (!isReserveSponsored(ctx.tx)) + { + JLOG(ctx.j.debug()) + << "preflight: spfSponsorReserve must be set when reassigning sponsorship"; + return temINVALID_FLAG; + } + if (ctx.tx.isFieldPresent(sfSponsee)) + { + JLOG(ctx.j.debug()) + << "preflight: sfSponsee must not be present when reassigning sponsorship"; + return temMALFORMED; + } + } + + if (ctx.tx.isFlag(tfSponsorshipEnd)) + { + // Ending sponsorship removes reserve sponsorship from a sponsored target. + // The target is sfSponsee when provided; otherwise it is sfAccount. + if (ctx.tx.isFieldPresent(sfSponsor)) + { + JLOG(ctx.j.debug()) + << "preflight: sfSponsor must not be present when ending sponsorship"; + return temMALFORMED; + } + + // sfSponsorFlags should not be present if it is ending sponsorship. + if (ctx.tx.isFieldPresent(sfSponsorFlags)) + { + // Unreachable: reaching here means sfSponsor is absent, which is already checked above, + // and preflight1Sponsor already rejects sfSponsorFlags present without sfSponsor with + // temINVALID_FLAG. Keep this as a defensive check. + // LCOV_EXCL_START + UNREACHABLE( + "xrpl::SponsorshipTransfer::preflight : sfSponsorFlags present without sfSponsor " + "when ending sponsorship"); + return temINVALID_FLAG; + // LCOV_EXCL_STOP + } + + if (ctx.tx.isFieldPresent(sfSponsee) && + ctx.tx.getAccountID(sfSponsee) == ctx.tx.getAccountID(sfAccount)) + { + JLOG(ctx.j.debug()) << "preflight: sfSponsee should not be the same as the account"; + return temMALFORMED; + } + } + + // Account-level reserve sponsorship changes the reserve responsibility for + // the account itself, so the new sponsor must explicitly co-sign. Object-level + // sponsorship may use pre-funded reserve sponsorship instead. + bool const isCreateOrReassign = + ctx.tx.isFlag(tfSponsorshipCreate) || ctx.tx.isFlag(tfSponsorshipReassign); + auto const reserveSponsor = getTxReserveSponsorID(ctx.tx); + bool const isAccountReserveSponsorship = + isCreateOrReassign && reserveSponsor && !ctx.tx.isFieldPresent(sfObjectID); + + if (isAccountReserveSponsorship && !ctx.tx.isFieldPresent(sfSponsorSignature)) + { + JLOG(ctx.j.debug()) << "preflight: account sponsorship requires sfSponsorSignature"; + return temMALFORMED; + } + + return tesSUCCESS; +} + +TER +SponsorshipTransfer::preclaim(PreclaimContext const& ctx) +{ + auto const objectID = ctx.tx[~sfObjectID]; + auto const newSponsorSleExpected = getTxReserveSponsor(ctx.view, ctx.tx); + if (!newSponsorSleExpected) + return newSponsorSleExpected.error(); // LCOV_EXCL_LINE + auto const newSponsorSle = *newSponsorSleExpected; + + auto const account = ctx.tx[sfAccount]; + auto const sponseeID = ctx.tx[~sfSponsee].value_or(account); + auto const sponseeSle = ctx.view.read(keylet::account(sponseeID)); + if (!sponseeSle) + { + // If it is ending sponsorship, sfSponsee is user input, return terNO_ACCOUNT if it does not + // exist. + if (ctx.tx.isFieldPresent(sfSponsee)) + return terNO_ACCOUNT; + + // If it is creating or reassigning sponsorship, sfSponsee is the account itself, which is + // always present by the time preclaim runs. Return tecINTERNAL if it does not exist. + return tecINTERNAL; // LCOV_EXCL_LINE + } + + // Default setup with an account sponsorship transfer. If it is an object transfer, they will be + // overridden to the object SLE and its type-specific sponsor field: + // sfHighSponsor/sfLowSponsor for a RippleState, sfSponsor for other object types. + SLE::const_pointer targetSle = sponseeSle; + auto const* sponsorField = &sfSponsor; + + if (objectID.has_value()) + { + auto const objectSle = ctx.view.read(keylet::unchecked(*objectID)); + if (!objectSle) + return tecNO_ENTRY; + + if (!isLedgerEntrySupportedBySponsorship(*objectSle)) + return tecNO_PERMISSION; + + if (!isLedgerEntryOwner(ctx.view, *objectSle, sponseeID)) + return tecNO_PERMISSION; + + // Object transfer: the target is the object, and its sponsor field + // depends on the object type, a RippleState stores the sponsor in + // sfHighSponsor/sfLowSponsor, while other object type uses sfSponsor. + targetSle = objectSle; + sponsorField = &getLedgerEntrySponsorField(*objectSle, sponseeID); + } + + bool const isSponsored = targetSle->isFieldPresent(*sponsorField); + + if (ctx.tx.isFlag(tfSponsorshipCreate)) + { + // Creating a new sponsorship: needs a new reserve sponsor, and the + // target must not already be sponsored + if (!newSponsorSle || isSponsored) + return tecNO_PERMISSION; + } + else if (ctx.tx.isFlag(tfSponsorshipReassign)) + { + // Reassigning sponsorship: needs a new reserve sponsor, and the target must already + // be sponsored + if (!newSponsorSle || !isSponsored) + return tecNO_PERMISSION; + + // Reassigning to the current sponsor would change no state, but would + // still draw down the sponsor's pre-funded reserve budget (and its + // reserve headroom would be double-counted in checkReserve). + if (targetSle->getAccountID(*sponsorField) == ctx.tx.getAccountID(sfSponsor)) + return tecNO_PERMISSION; + } + else if (ctx.tx.isFlag(tfSponsorshipEnd)) + { + // Ending sponsorship: no new reserve sponsor, the target must be sponsored. + if (newSponsorSle || !isSponsored) + return tecNO_PERMISSION; + + // Only the sponsor or sponsee can end sponsorship. + auto const sponsor = targetSle->getAccountID(*sponsorField); + if (account != sponsor && account != sponseeID) + return tecNO_PERMISSION; + } + + return tesSUCCESS; +} + +TER +SponsorshipTransfer::doApply() +{ + auto const objectID = ctx_.tx[~sfObjectID]; + + auto const sponseeID = ctx_.tx[~sfSponsee].value_or(accountID_); + auto const sponseeSle = view().peek(keylet::account(sponseeID)); + if (!sponseeSle) + return tefINTERNAL; // LCOV_EXCL_LINE + + auto const balanceBeforeFee = [&](SLE::const_ref sle) -> XRPAmount { + if (sle->getAccountID(sfAccount) == accountID_) + return preFeeBalance_; + return sle->getFieldAmount(sfBalance).xrp(); + }; + + bool const isCreate = ctx_.tx.isFlag(tfSponsorshipCreate); + bool const isReassign = ctx_.tx.isFlag(tfSponsorshipReassign); + + if (objectID.has_value()) + { + // Transfer object sponsor + auto const objectSle = view().peek(keylet::unchecked(*objectID)); + if (!objectSle) + return tefINTERNAL; // LCOV_EXCL_LINE + + // preclaim established that the sponsee owns the object. + if (!isLedgerEntryOwner(view(), *objectSle, sponseeID)) + return tefINTERNAL; // LCOV_EXCL_LINE + + auto const ownerSle = view().peek(keylet::account(sponseeID)); + if (!ownerSle) + return tefINTERNAL; // LCOV_EXCL_LINE + + auto const ownerCountDelta = + static_cast(getLedgerEntryOwnerCount(*objectSle)); + auto const& sponsorField = getLedgerEntrySponsorField(*objectSle, sponseeID); + + if (isCreate || isReassign) + { + auto const newSponsor = ctx_.tx[~sfSponsor]; + XRPL_ASSERT( + newSponsor.has_value(), + "xrpl::SponsorshipTransfer::doApply : sfSponsor present for object sponsor " + "create/reassign"); + if (!newSponsor) + return tefINTERNAL; // LCOV_EXCL_LINE + auto const newSponsorID = *newSponsor; + auto const newSponsorSle = view().peek(keylet::account(newSponsorID)); + if (!newSponsorSle) + return tefINTERNAL; // LCOV_EXCL_LINE + + // Check if new sponsor has sufficient balance + // NOLINTNEXTLINE(readability-suspicious-call-argument) + if (auto const ter = checkReserve( + ctx_.getApplyViewContext(), + sponseeSle, + sponseeSle->getFieldAmount(sfBalance).xrp(), + newSponsorSle, + {.ownerCountDelta = ownerCountDelta}, + ctx_.journal); + !isTesSuccess(ter)) + return ter; + + if (isCreate) + { + // Update owner's sponsored count + if (auto const ter = incrementSponsorCount( + view(), ownerSle, sfSponsoredOwnerCount, ownerCountDelta); + !isTesSuccess(ter)) + return ter; // LCOV_EXCL_LINE + } + else if (isReassign) + { + auto const oldSponsorID = objectSle->getAccountID(sponsorField); + if (!oldSponsorID) + return tefINTERNAL; // LCOV_EXCL_LINE + auto const oldSponsorSle = view().peek(keylet::account(oldSponsorID)); + if (!oldSponsorSle) + return tefINTERNAL; // LCOV_EXCL_LINE + + // Decrement old sponsor's sponsoring count + if (auto const ter = decrementSponsorCount( + view(), oldSponsorSle, sfSponsoringOwnerCount, ownerCountDelta); + !isTesSuccess(ter)) + return ter; // LCOV_EXCL_LINE + } + + // Increment new sponsor's sponsoring count + if (auto const ter = incrementSponsorCount( + view(), newSponsorSle, sfSponsoringOwnerCount, ownerCountDelta); + !isTesSuccess(ter)) + return ter; // LCOV_EXCL_LINE + + // Object is now sponsored by new sponsor + objectSle->setAccountID(sponsorField, newSponsorID); + view().update(objectSle); + + auto const sponsorshipSle = view().peek(keylet::sponsorship(newSponsorID, sponseeID)); + if (sponsorshipSle) + { + // Update ReserveCount for sponsorship object if it exists + if (auto const ter = + decrementPrefundedReserveCount(view(), sponsorshipSle, ownerCountDelta); + !isTesSuccess(ter)) + return ter; // LCOV_EXCL_LINE + } + } + else if (ctx_.tx.isFlag(tfSponsorshipEnd)) + { + // End object sponsor + auto const oldSponsorID = objectSle->getAccountID(sponsorField); + if (!oldSponsorID) + return tefINTERNAL; // LCOV_EXCL_LINE + auto const oldSponsorSle = view().peek(keylet::account(oldSponsorID)); + if (!oldSponsorSle) + return tefINTERNAL; // LCOV_EXCL_LINE + + // The owner reclaims the reserve burden when the object is no longer sponsored. + // We do not check the sponsee's reserve here (via `checkReserve`) so that a sponsor can + // always end a sponsorship, even if the sponsee lacks sufficient reserve. + + // Decrement sponsored count + if (auto const ter = decrementSponsorCount( + view(), sponseeSle, sfSponsoredOwnerCount, ownerCountDelta); + !isTesSuccess(ter)) + return ter; // LCOV_EXCL_LINE + + // Decrement old sponsoring count + if (auto const ter = decrementSponsorCount( + view(), oldSponsorSle, sfSponsoringOwnerCount, ownerCountDelta); + !isTesSuccess(ter)) + return ter; // LCOV_EXCL_LINE + + // Remove sponsor from object + objectSle->makeFieldAbsent(sponsorField); + view().update(objectSle); + } + } + else + { + // Account-level sponsorship is always co-signed (preflight requires + // sfSponsorSignature), so there is no pre-funded budget to draw down here. + if (isCreate || isReassign) + { + auto const newSponsor = ctx_.tx[~sfSponsor]; + XRPL_ASSERT( + newSponsor.has_value(), + "xrpl::SponsorshipTransfer::doApply : sfSponsor present for account sponsor " + "create/reassign"); + if (!newSponsor) + return tefINTERNAL; // LCOV_EXCL_LINE + auto const newSponsorID = *newSponsor; + auto const newSponsorSle = view().peek(keylet::account(newSponsorID)); + if (!newSponsorSle) + return tefINTERNAL; // LCOV_EXCL_LINE + + if (auto const ter = checkReserve( + ctx_.getApplyViewContext(), + sponseeSle, + sponseeSle->getFieldAmount(sfBalance).xrp(), + newSponsorSle, + {.accountCountDelta = 1}, + ctx_.journal); + !isTesSuccess(ter)) + return ter; + + if (isReassign) + { + auto const oldSponsorID = sponseeSle->getAccountID(sfSponsor); + if (!oldSponsorID) + return tefINTERNAL; // LCOV_EXCL_LINE + auto const oldSponsorSle = view().peek(keylet::account(oldSponsorID)); + if (!oldSponsorSle) + return tefINTERNAL; // LCOV_EXCL_LINE + + // Decrement old sponsoring count + if (auto const ter = + decrementSponsorCount(view(), oldSponsorSle, sfSponsoringAccountCount, 1); + !isTesSuccess(ter)) + return ter; // LCOV_EXCL_LINE + } + + // Increment new sponsoring count + if (auto const ter = + incrementSponsorCount(view(), newSponsorSle, sfSponsoringAccountCount, 1); + !isTesSuccess(ter)) + return ter; // LCOV_EXCL_LINE + + // Account is now sponsored by new sponsor + sponseeSle->setAccountID(sfSponsor, newSponsorID); + view().update(sponseeSle); + } + else if (ctx_.tx.isFlag(tfSponsorshipEnd)) + { + // End account sponsor + auto const oldSponsorID = sponseeSle->getAccountID(sfSponsor); + if (!oldSponsorID) + return tefINTERNAL; // LCOV_EXCL_LINE + auto const oldSponsorSle = view().peek(keylet::account(oldSponsorID)); + if (!oldSponsorSle) + return tefINTERNAL; // LCOV_EXCL_LINE + + // The sponsee must be able to hold its own account reserve after + // the sponsorship is removed. + if (auto const ter = checkReserve( + ctx_.getApplyViewContext(), + sponseeSle, + balanceBeforeFee(sponseeSle), + SLE::pointer(), + {.accountCountDelta = 1}, + ctx_.journal); + !isTesSuccess(ter)) + return ter; + + sponseeSle->makeFieldAbsent(sfSponsor); + view().update(sponseeSle); + + // Decrement account sponsoring count + if (auto const ter = + decrementSponsorCount(view(), oldSponsorSle, sfSponsoringAccountCount, 1); + !isTesSuccess(ter)) + return ter; // LCOV_EXCL_LINE + } + } + + return tesSUCCESS; +} + +void +SponsorshipTransfer::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) +{ +} + +bool +SponsorshipTransfer::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/transactors/system/Batch.cpp b/src/libxrpl/tx/transactors/system/Batch.cpp index ba54c14b1e..dee465cb88 100644 --- a/src/libxrpl/tx/transactors/system/Batch.cpp +++ b/src/libxrpl/tx/transactors/system/Batch.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -227,6 +228,16 @@ Batch::preflight(PreflightContext const& ctx) return temINVALID_FLAG; } + if (ctx.tx.isFieldPresent(sfSponsorFlags)) + { + if (isReserveSponsored(ctx.tx)) + { + JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:" + << "spfSponsorReserve is not allowed on outer Batch."; + return temINVALID_FLAG; + } + } + auto const& rawTxns = ctx.tx.getFieldArray(sfRawTransactions); if (rawTxns.size() <= 1) { @@ -331,6 +342,14 @@ Batch::preflight(PreflightContext const& ctx) return ret; } } + if (stx.isFieldPresent(sfSponsorSignature)) + { + auto const sponsorSignature = stx.getFieldObject(sfSponsorSignature); + if (auto const ret = checkSignatureFields(sponsorSignature, hash, "sponsor signature ")) + { + return ret; + } + } // Check that the Fee is native asset (XRP) and zero if (auto const fee = stx.getFieldAmount(sfFee); !fee.native() || fee.xrp() != beast::kZero) @@ -341,6 +360,10 @@ Batch::preflight(PreflightContext const& ctx) return temBAD_FEE; } + // Disallow fee sponsorship on Batch inner txs + if (stx.isFieldPresent(sfSponsor) && isFeeSponsored(stx)) + return temINVALID_FLAG; + auto const innerAccount = stx.getAccountID(sfAccount); if (auto const preflightResult = xrpl::preflight(ctx.registry, ctx.rules, parentBatchId, stx, TapBatch, ctx.j); @@ -422,7 +445,7 @@ Batch::preflightSigValidated(PreflightContext const& ctx) { // A delegated inner is signed by the delegate, not the account holder, // so the delegate is the required signer when present. - AccountID const authorizer = rb.getFeePayer(); + AccountID const authorizer = rb.getInitiator(); // The outer account signs the batch itself, so it is never added to the // required signers. @@ -433,6 +456,10 @@ Batch::preflightSigValidated(PreflightContext const& ctx) if (auto const counterparty = rb[~sfCounterparty]; counterparty && counterparty != outerAccount) requiredSigners.push_back(*counterparty); + + if (auto const sponsor = rb.at(~sfSponsor); + sponsor && rb.isFieldPresent(sfSponsorSignature) && sponsor != outerAccount) + requiredSigners.push_back(*sponsor); } std::ranges::sort(requiredSigners); auto const dupes = std::ranges::unique(requiredSigners); diff --git a/src/libxrpl/tx/transactors/system/TicketCreate.cpp b/src/libxrpl/tx/transactors/system/TicketCreate.cpp index 73a72c217d..e19dc9fe96 100644 --- a/src/libxrpl/tx/transactors/system/TicketCreate.cpp +++ b/src/libxrpl/tx/transactors/system/TicketCreate.cpp @@ -75,13 +75,12 @@ TicketCreate::doApply() // check the starting balance because we want to allow dipping into the // reserve to pay fees. std::uint32_t const ticketCount = ctx_.tx[sfTicketCount]; - { - XRPAmount const reserve = - view().fees().accountReserve(sleAccountRoot->getFieldU32(sfOwnerCount) + ticketCount); - - if (preFeeBalance_ < reserve) - return tecINSUFFICIENT_RESERVE; - } + if (preFeeBalance_ < accountReserve( + view(), + sleAccountRoot, + j_, + {.ownerCountDelta = static_cast(ticketCount)})) + return tecINSUFFICIENT_RESERVE; beast::Journal const viewJ{ctx_.registry.get().getJournal("View")}; @@ -105,6 +104,7 @@ TicketCreate::doApply() sleTicket->setAccountID(sfAccount, accountID_); sleTicket->setFieldU32(sfTicketSequence, curTicketSeq); + view().insert(sleTicket); auto const page = view().dirInsert( @@ -125,7 +125,7 @@ TicketCreate::doApply() sleAccountRoot->setFieldU32(sfTicketCount, oldTicketCount + ticketCount); // Every added Ticket counts against the creator's reserve. - adjustOwnerCount(view(), sleAccountRoot, ticketCount, viewJ); + increaseOwnerCount(view(), sleAccountRoot, {}, ticketCount, viewJ); // TicketCreate is the only transaction that can cause an account root's // Sequence field to increase by more than one. October 2018. diff --git a/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp b/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp index 6db37515cb..0aeb6f33d1 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp @@ -164,7 +164,7 @@ MPTokenAuthorize::doApply() { auto const& tx = ctx_.tx; return authorizeMPToken( - ctx_.view(), + ctx_.getApplyViewContext(), preFeeBalance_, tx[sfMPTokenIssuanceID], accountID_, diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp index e30127b688..aad1642f68 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -113,22 +114,35 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx) } std::expected -MPTokenIssuanceCreate::create(ApplyView& view, beast::Journal journal, MPTCreateArgs const& args) +MPTokenIssuanceCreate::create( + ApplyViewContext ctx, + beast::Journal journal, + MPTCreateArgs const& args) { - auto const acct = view.peek(keylet::account(args.account)); + auto const acct = ctx.view.peek(keylet::account(args.account)); if (!acct) return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE - if (args.priorBalance && - *(args.priorBalance) < view.fees().accountReserve((*acct)[sfOwnerCount] + 1)) - return std::unexpected(tecINSUFFICIENT_RESERVE); + // A reserve sponsor only covers tx.Account's own objects. + auto const sponsorExp = getEffectiveTxReserveSponsor(ctx, acct); + if (!sponsorExp) + return std::unexpected(sponsorExp.error()); // LCOV_EXCL_LINE + auto const sponsorSle = *sponsorExp; + + if (args.priorBalance) + { + if (auto const ret = checkReserve( + ctx, acct, *(args.priorBalance), sponsorSle, {.ownerCountDelta = 1}, journal); + !isTesSuccess(ret)) + return std::unexpected(ret); + } auto const mptId = makeMptID(args.sequence, args.account); auto const mptIssuanceKeylet = keylet::mptokenIssuance(mptId); // create the MPTokenIssuance { - auto const ownerNode = view.dirInsert( + auto const ownerNode = ctx.view.dirInsert( keylet::ownerDir(args.account), mptIssuanceKeylet, describeOwnerDir(args.account)); if (!ownerNode) @@ -166,7 +180,7 @@ MPTokenIssuanceCreate::create(ApplyView& view, beast::Journal journal, MPTCreate // populate this after the pseudo-account's MPToken / // RippleState has been installed. A missing holding here // would dangle the pointer and is a programmer error. - auto const sleHolding = view.read(keylet::unchecked(*args.referenceHolding)); + auto const sleHolding = ctx.view.read(keylet::unchecked(*args.referenceHolding)); if (!sleHolding) return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE auto const type = sleHolding->getType(); @@ -175,11 +189,13 @@ MPTokenIssuanceCreate::create(ApplyView& view, beast::Journal journal, MPTCreate (*mptIssuance)[sfReferenceHolding] = *args.referenceHolding; } - view.insert(mptIssuance); + addSponsorToLedgerEntry(mptIssuance, sponsorSle); + + ctx.view.insert(mptIssuance); } // Update owner count. - adjustOwnerCount(view, acct, 1, journal); + increaseOwnerCount(ctx.view, acct, sponsorSle, 1, journal); return mptId; } @@ -189,7 +205,7 @@ MPTokenIssuanceCreate::doApply() { auto const& tx = ctx_.tx; auto const result = create( - view(), + ctx_.getApplyViewContext(), j_, { .priorBalance = preFeeBalance_, diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp index c56697767b..05d39c3596 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp @@ -49,10 +49,9 @@ MPTokenIssuanceDestroy::doApply() if (!view().dirRemove(keylet::ownerDir(accountID_), (*mpt)[sfOwnerNode], mpt->key(), false)) return tefBAD_LEDGER; // LCOV_EXCL_LINE + decreaseOwnerCountForObject(view(), accountID_, mpt, 1, j_); view().erase(mpt); - adjustOwnerCount(view(), view().peek(keylet::account(accountID_)), -1, j_); - return tesSUCCESS; } diff --git a/src/libxrpl/tx/transactors/token/TrustSet.cpp b/src/libxrpl/tx/transactors/token/TrustSet.cpp index e79e3cad5a..3d078ce825 100644 --- a/src/libxrpl/tx/transactors/token/TrustSet.cpp +++ b/src/libxrpl/tx/transactors/token/TrustSet.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -297,8 +298,6 @@ TrustSet::doApply() if (!sle) return tefINTERNAL; // LCOV_EXCL_LINE - std::uint32_t const uOwnerCount = sle->getFieldU32(sfOwnerCount); - // The reserve that is required to create the line. Note // that although the reserve increases with every item // an account owns, in the case of trust lines we only @@ -317,9 +316,23 @@ TrustSet::doApply() // well. A person with no intention of using the gateway // could use the extra XRP for their own purposes. + auto const sponsorExp = getTxReserveSponsor(ctx_.getApplyViewContext()); + if (!sponsorExp) + return sponsorExp.error(); // LCOV_EXCL_LINE + auto const sponsorSle = *sponsorExp; + + auto getSponsor = [&sponsorSle, this](AccountID const& account) { + return (sponsorSle && account == accountID_) ? sponsorSle : SLE::pointer(); + }; + + // The "free-tier" shortcut (ownerCount < 2) only applies when there is no sponsor. + // With any sponsor on the tx, the sponsor must cover the reserve (via balance or + // prefunded budget), so the reserve check always runs. + bool const freeTrustLine = !sponsorSle && (ownerCount(sle, j_) < 2); + std::uint32_t const uOwnerCount = ownerCount(sle, j_); XRPAmount const reserveCreate( (uOwnerCount < 2) ? XRPAmount(beast::kZero) - : view().fees().accountReserve(uOwnerCount + 1)); + : accountReserve(view(), sle, j_, {.ownerCountDelta = 1})); std::uint32_t const uQualityIn(bQualityIn ? ctx_.tx.getFieldU32(sfQualityIn) : 0); std::uint32_t uQualityOut(bQualityOut ? ctx_.tx.getFieldU32(sfQualityOut) : 0); @@ -505,6 +518,11 @@ TrustSet::doApply() bool bReserveIncrease = false; + auto const currentHighSponsor = + getLedgerEntryReserveSponsor(view(), sleRippleState, sfHighSponsor); + auto const currentLowSponsor = + getLedgerEntryReserveSponsor(view(), sleRippleState, sfLowSponsor); + if (bSetAuth) { uFlagsOut |= (bHigh ? lsfHighAuth : lsfLowAuth); @@ -512,10 +530,30 @@ TrustSet::doApply() if (bLowReserveSet && !bLowReserved) { + SLE::pointer const lowSponsor = getSponsor(uLowAccountID); + + if (view().rules().enabled(featureSponsor)) + { + if (auto const ret = checkReserve( + ctx_.getApplyViewContext(), + sleLowAccount, + preFeeBalance_, + lowSponsor, + {.ownerCountDelta = 1}, + j_, + tecINSUF_RESERVE_LINE); + lowSponsor && !isTesSuccess(ret)) + { + return ret; + } + } + // Set reserve for low account. - adjustOwnerCount(view(), sleLowAccount, 1, viewJ); + increaseOwnerCount(view(), sleLowAccount, lowSponsor, 1, viewJ); uFlagsOut |= lsfLowReserve; + addSponsorToLedgerEntry(sleRippleState, lowSponsor, sfLowSponsor); + if (!bHigh) bReserveIncrease = true; } @@ -523,16 +561,41 @@ TrustSet::doApply() if (bLowReserveClear && bLowReserved) { // Clear reserve for low account. - adjustOwnerCount(view(), sleLowAccount, -1, viewJ); + decreaseOwnerCount(view(), sleLowAccount, currentLowSponsor, 1, viewJ); uFlagsOut &= ~lsfLowReserve; + + removeSponsorFromLedgerEntry(sleRippleState, sfLowSponsor); } if (bHighReserveSet && !bHighReserved) { + SLE::pointer const highSponsor = getSponsor(uHighAccountID); + + // should be checked PreFunded Sponsor before increaseOwnerCount() + // For PreFunded sponsors, we need to check if there are sufficient reserves before + // calling increaseOwnerCount(). + if (view().rules().enabled(featureSponsor)) + { + if (auto const ret = checkReserve( + ctx_.getApplyViewContext(), + sleHighAccount, + preFeeBalance_, + highSponsor, + {.ownerCountDelta = 1}, + j_, + tecINSUF_RESERVE_LINE); + highSponsor && !isTesSuccess(ret)) + { + return ret; + } + } + // Set reserve for high account. - adjustOwnerCount(view(), sleHighAccount, 1, viewJ); + increaseOwnerCount(view(), sleHighAccount, highSponsor, 1, viewJ); uFlagsOut |= lsfHighReserve; + addSponsorToLedgerEntry(sleRippleState, highSponsor, sfHighSponsor); + if (bHigh) bReserveIncrease = true; } @@ -540,34 +603,75 @@ TrustSet::doApply() if (bHighReserveClear && bHighReserved) { // Clear reserve for high account. - adjustOwnerCount(view(), sleHighAccount, -1, viewJ); + decreaseOwnerCount(view(), sleHighAccount, currentHighSponsor, 1, viewJ); uFlagsOut &= ~lsfHighReserve; + + removeSponsorFromLedgerEntry(sleRippleState, sfHighSponsor); } if (uFlagsIn != uFlagsOut) sleRippleState->setFieldU32(sfFlags, uFlagsOut); - if (bDefault || badCurrency() == currency) + if (view().rules().enabled(featureSponsor)) { - // Delete. + if (bDefault || badCurrency() == currency) + { + // Delete. - terResult = trustDelete(view(), sleRippleState, uLowAccountID, uHighAccountID, viewJ); - } - // Reserve is not scaled by load. - else if (bReserveIncrease && preFeeBalance_ < reserveCreate) - { - JLOG(j_.trace()) << "Delay transaction: Insufficent reserve to " - "add trust line."; + terResult = + trustDelete(view(), sleRippleState, uLowAccountID, uHighAccountID, viewJ); + } + // Reserve is not scaled by load + else if ( + auto const ret = checkReserve( + ctx_.getApplyViewContext(), + sle, + preFeeBalance_, + sponsorSle, + {}, + j_, + tecINSUF_RESERVE_LINE); + !freeTrustLine && bReserveIncrease && !isTesSuccess(ret)) + { + JLOG(j_.trace()) << "Delay transaction: Insufficent reserve to " + "add trust line."; - // Another transaction could provide XRP to the account and then - // this transaction would succeed. - terResult = tecINSUF_RESERVE_LINE; + // Another transaction could provide XRP to the account and then + // this transaction would succeed. + terResult = ret; + } + else + { + view().update(sleRippleState); + + JLOG(j_.trace()) << "Modify ripple line"; + } } else { - view().update(sleRippleState); + if (bDefault || badCurrency() == currency) + { + // Delete. - JLOG(j_.trace()) << "Modify ripple line"; + terResult = + trustDelete(view(), sleRippleState, uLowAccountID, uHighAccountID, viewJ); + } + // Reserve is not scaled by load. + else if (bReserveIncrease && preFeeBalance_ < reserveCreate) + { + JLOG(j_.trace()) << "Delay transaction: Insufficent reserve to " + "add trust line."; + + // Another transaction could provide XRP to the account and then + // this transaction would succeed. + terResult = tecINSUF_RESERVE_LINE; + } + else + { + view().update(sleRippleState); + + JLOG(j_.trace()) << "Modify ripple line"; + } } } // Line does not exist. @@ -582,8 +686,8 @@ TrustSet::doApply() JLOG(j_.trace()) << "Redundant: Setting non-existent ripple line to defaults."; return tecNO_LINE_REDUNDANT; } - else if (preFeeBalance_ < reserveCreate) // Reserve is not scaled by - // load. + // reserve is not scaled by load + else if (!view().rules().enabled(featureSponsor) && preFeeBalance_ < reserveCreate) { JLOG(j_.trace()) << "Delay transaction: Line does not exist. " "Insufficent reserve to create line."; @@ -592,6 +696,25 @@ TrustSet::doApply() // transaction would succeed. terResult = tecNO_LINE_INSUF_RESERVE; } + else if ( + auto const ret = checkReserve( + ctx_.getApplyViewContext(), + sle, + preFeeBalance_, + sponsorSle, + {.ownerCountDelta = 1}, + j_, + tecNO_LINE_INSUF_RESERVE); + view().rules().enabled(featureSponsor) && !freeTrustLine && + !isTesSuccess(ret)) // Reserve is not scaled by load. + { + JLOG(j_.trace()) << "Delay transaction: Line does not exist. " + "Insufficent reserve to create line."; + + // Another transaction could create the account and then this + // transaction would succeed. + terResult = ret; + } else { // Zero balance in currency. @@ -617,6 +740,7 @@ TrustSet::doApply() saLimitAllow, // Limit for who is being charged. uQualityIn, uQualityOut, + sponsorSle, viewJ); } diff --git a/src/libxrpl/tx/transactors/vault/VaultClawback.cpp b/src/libxrpl/tx/transactors/vault/VaultClawback.cpp index 541660c98f..d77286b667 100644 --- a/src/libxrpl/tx/transactors/vault/VaultClawback.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultClawback.cpp @@ -389,8 +389,8 @@ VaultClawback::doApply() auto const& vaultAccount = vault->at(sfAccount); // Transfer shares from holder to vault. - if (auto const ter = - accountSend(view(), holder, vaultAccount, sharesDestroyed, j_, WaiveTransferFee::Yes); + if (auto const ter = accountSend( + view(), holder, vaultAccount, sharesDestroyed, j_, {}, WaiveTransferFee::Yes); !isTesSuccess(ter)) return ter; @@ -399,7 +399,8 @@ VaultClawback::doApply() // Keep MPToken if holder is the vault owner. if (holder != vault->at(sfOwner)) { - if (auto const ter = removeEmptyHolding(view(), holder, sharesDestroyed.asset(), j_); + if (auto const ter = + removeEmptyHolding(ctx_.getApplyViewContext(), holder, sharesDestroyed.asset(), j_); isTesSuccess(ter)) { JLOG(j_.debug()) // @@ -425,7 +426,7 @@ VaultClawback::doApply() { // Transfer assets from vault to issuer. if (auto const ter = accountSend( - view(), vaultAccount, accountID_, assetsRecovered, j_, WaiveTransferFee::Yes); + view(), vaultAccount, accountID_, assetsRecovered, j_, {}, WaiveTransferFee::Yes); !isTesSuccess(ter)) return ter; diff --git a/src/libxrpl/tx/transactors/vault/VaultCreate.cpp b/src/libxrpl/tx/transactors/vault/VaultCreate.cpp index d262132b6f..e1f5873a89 100644 --- a/src/libxrpl/tx/transactors/vault/VaultCreate.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultCreate.cpp @@ -146,6 +146,7 @@ VaultCreate::doApply() // we can consider downgrading them to `tef` or `tem`. auto const& tx = ctx_.tx; + auto applyViewContext = ctx_.getApplyViewContext(); auto const sequence = tx.getSeqValue(); auto const owner = view().peek(keylet::account(accountID_)); if (owner == nullptr) @@ -156,9 +157,8 @@ VaultCreate::doApply() if (auto ter = dirLink(view(), accountID_, vault)) return ter; // We will create Vault and PseudoAccount, hence increase OwnerCount by 2 - adjustOwnerCount(view(), owner, 2, j_); - auto const ownerCount = owner->at(sfOwnerCount); - if (preFeeBalance_ < view().fees().accountReserve(ownerCount)) + increaseOwnerCount(view(), owner, {}, 2, j_); + if (preFeeBalance_ < accountReserve(view(), owner, j_)) return tecINSUFFICIENT_RESERVE; auto maybePseudo = createPseudoAccount(view(), vault->key(), sfVaultID); @@ -168,7 +168,8 @@ VaultCreate::doApply() AccountID const pseudoId = pseudo->at(sfAccount); auto const asset = tx[sfAsset]; - if (auto ter = addEmptyHolding(view(), pseudoId, preFeeBalance_, asset, j_); !isTesSuccess(ter)) + if (auto ter = addEmptyHolding(applyViewContext, pseudoId, preFeeBalance_, asset, j_); + !isTesSuccess(ter)) return ter; std::uint8_t const scale = (asset.holds() || asset.native()) @@ -197,7 +198,7 @@ VaultCreate::doApply() : keylet::trustLine(pseudoId, asset.get()).key; }(); auto const maybeShare = MPTokenIssuanceCreate::create( - view(), + applyViewContext, j_, { .priorBalance = std::nullopt, @@ -243,8 +244,8 @@ VaultCreate::doApply() view().insert(vault); // Explicitly create MPToken for the vault owner - if (auto const err = - authorizeMPToken(view(), preFeeBalance_, mptIssuanceID, accountID_, ctx_.journal); + if (auto const err = authorizeMPToken( + applyViewContext, preFeeBalance_, mptIssuanceID, accountID_, ctx_.journal); !isTesSuccess(err)) return err; @@ -252,7 +253,13 @@ VaultCreate::doApply() if (tx.isFlag(tfVaultPrivate)) { if (auto const err = authorizeMPToken( - view(), preFeeBalance_, mptIssuanceID, pseudoId, ctx_.journal, {}, accountID_); + applyViewContext, + preFeeBalance_, + mptIssuanceID, + pseudoId, + ctx_.journal, + {}, + accountID_); !isTesSuccess(err)) return err; } diff --git a/src/libxrpl/tx/transactors/vault/VaultDelete.cpp b/src/libxrpl/tx/transactors/vault/VaultDelete.cpp index fa38ae278b..497a2f2465 100644 --- a/src/libxrpl/tx/transactors/vault/VaultDelete.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultDelete.cpp @@ -96,13 +96,15 @@ TER VaultDelete::doApply() { auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID])); + auto applyViewContext = ctx_.getApplyViewContext(); if (!vault) return tefINTERNAL; // LCOV_EXCL_LINE // Destroy the asset holding. auto asset = vault->at(sfAsset); - if (auto ter = removeEmptyHolding(view(), vault->at(sfAccount), asset, j_); !isTesSuccess(ter)) + if (auto ter = removeEmptyHolding(applyViewContext, vault->at(sfAccount), asset, j_); + !isTesSuccess(ter)) return ter; auto const& pseudoID = vault->at(sfAccount); @@ -130,7 +132,8 @@ VaultDelete::doApply() // Try to remove MPToken for vault shares for the vault owner if it exists. if (auto const mptoken = view().peek(keylet::mptoken(shareMPTID, accountID_))) { - if (auto const ter = removeEmptyHolding(view(), accountID_, MPTIssue(shareMPTID), j_); + if (auto const ter = + removeEmptyHolding(applyViewContext, accountID_, MPTIssue(shareMPTID), j_); !isTesSuccess(ter)) { // LCOV_EXCL_START @@ -151,7 +154,7 @@ VaultDelete::doApply() return tefBAD_LEDGER; // LCOV_EXCL_STOP } - adjustOwnerCount(view(), pseudoAcct, -1, j_); + decreaseOwnerCountForObject(view(), pseudoAcct, mpt, 1, j_); view().erase(mpt); @@ -210,7 +213,7 @@ VaultDelete::doApply() } // We are destroying Vault and PseudoAccount, hence decrease by 2 - adjustOwnerCount(view(), owner, -2, j_); + decreaseOwnerCountForObject(view(), owner, vault, 2, j_); // Destroy the vault. view().erase(vault); diff --git a/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp b/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp index d5eb80b84d..aa9cfc8537 100644 --- a/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp @@ -23,6 +23,7 @@ #include #include +#include #include namespace xrpl { @@ -196,6 +197,7 @@ VaultDeposit::doApply() { bool const fix320Enabled = view().rules().enabled(fixCleanup3_2_0); auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID])); + auto applyViewContext = ctx_.getApplyViewContext(); if (!vault) return tefINTERNAL; // LCOV_EXCL_LINE auto const vaultAsset = vault->at(sfAsset); @@ -230,7 +232,7 @@ VaultDeposit::doApply() if (vault->isFlag(lsfVaultPrivate) && accountID_ != vault->at(sfOwner)) { if (auto const err = enforceMPTokenAuthorization( - ctx_.view(), mptIssuanceID, accountID_, preFeeBalance_, j_); + applyViewContext, mptIssuanceID, accountID_, preFeeBalance_, j_); !isTesSuccess(err)) return err; } @@ -240,7 +242,11 @@ VaultDeposit::doApply() if (!view().exists(keylet::mptoken(mptIssuanceID, accountID_))) { if (auto const err = authorizeMPToken( - view(), preFeeBalance_, mptIssuanceID->value(), accountID_, ctx_.journal); + applyViewContext, + preFeeBalance_, + mptIssuanceID->value(), + accountID_, + ctx_.journal); !isTesSuccess(err)) return err; } @@ -252,7 +258,7 @@ VaultDeposit::doApply() XRPL_ASSERT( accountID_ == vault->at(sfOwner), "xrpl::VaultDeposit::doApply : account is owner"); if (auto const err = authorizeMPToken( - view(), + applyViewContext, preFeeBalance_, // priorBalance mptIssuanceID->value(), // mptIssuanceID sleIssuance->at(sfIssuer), // account @@ -319,7 +325,7 @@ VaultDeposit::doApply() // Transfer assets from depositor to vault. if (auto const ter = accountSend( - view(), accountID_, vaultAccount, assetsDeposited, j_, WaiveTransferFee::Yes); + view(), accountID_, vaultAccount, assetsDeposited, j_, {}, WaiveTransferFee::Yes); !isTesSuccess(ter)) return ter; @@ -346,8 +352,8 @@ VaultDeposit::doApply() } // Transfer shares from vault to depositor. - if (auto const ter = - accountSend(view(), vaultAccount, accountID_, sharesCreated, j_, WaiveTransferFee::Yes); + if (auto const ter = accountSend( + view(), vaultAccount, accountID_, sharesCreated, j_, {}, WaiveTransferFee::Yes); !isTesSuccess(ter)) return ter; diff --git a/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp b/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp index ad91c55723..353b72c30d 100644 --- a/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp @@ -194,6 +194,7 @@ TER VaultWithdraw::doApply() { auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID])); + auto applyViewContext = ctx_.getApplyViewContext(); if (!vault) return tefINTERNAL; // LCOV_EXCL_LINE @@ -349,9 +350,10 @@ VaultWithdraw::doApply() view().update(vault); auto const& vaultAccount = vault->at(sfAccount); + // Transfer shares from depositor to vault. if (auto const ter = accountSend( - view(), accountID_, vaultAccount, sharesRedeemed, j_, WaiveTransferFee::Yes); + view(), accountID_, vaultAccount, sharesRedeemed, j_, {}, WaiveTransferFee::Yes); !isTesSuccess(ter)) return ter; @@ -360,7 +362,8 @@ VaultWithdraw::doApply() // Keep MPToken if holder is the vault owner. if (accountID_ != vault->at(sfOwner)) { - if (auto const ter = removeEmptyHolding(view(), accountID_, sharesRedeemed.asset(), j_); + if (auto const ter = + removeEmptyHolding(applyViewContext, accountID_, sharesRedeemed.asset(), j_); isTesSuccess(ter)) { JLOG(j_.debug()) // @@ -386,7 +389,7 @@ VaultWithdraw::doApply() auto const dstAcct = ctx_.tx[~sfDestination].value_or(accountID_); return doWithdraw( - view(), ctx_.tx, accountID_, dstAcct, vaultAccount, preFeeBalance_, assetsWithdrawn, j_); + applyViewContext, accountID_, dstAcct, vaultAccount, preFeeBalance_, assetsWithdrawn, j_); } void diff --git a/src/test/app/AMMCalc_test.cpp b/src/test/app/AMMCalc_test.cpp index 3c4a62bfd3..74080e669c 100644 --- a/src/test/app/AMMCalc_test.cpp +++ b/src/test/app/AMMCalc_test.cpp @@ -31,7 +31,8 @@ namespace xrpl::test { -/** AMM Calculator. Uses AMM formulas to simulate the payment engine +/** + * AMM Calculator. Uses AMM formulas to simulate the payment engine * expected results. Assuming the formulas are correct some unit-tests can * be verified. Currently supported operations are: * - swapIn, find out given in. in can flow through multiple AMM/Offer steps. diff --git a/src/test/app/AMMExtendedMPT_test.cpp b/src/test/app/AMMExtendedMPT_test.cpp index e6dcc95733..f04ea39f2b 100644 --- a/src/test/app/AMMExtendedMPT_test.cpp +++ b/src/test/app/AMMExtendedMPT_test.cpp @@ -463,7 +463,7 @@ private: // Provide micro amounts to compensate for fees to make results round // nice. auto const startingXrp = - XRP(100) + env.current()->fees().accountReserve(2) + env.current()->fees().base * 3; + XRP(100) + env.current()->fees().accountReserve(2, 1) + env.current()->fees().base * 3; env.fund(startingXrp, gw_, alice_); env.fund(XRP(2'000), bob_); @@ -2296,8 +2296,10 @@ private: // 1,400e12 - 56.3368e12*1.25 = 1400e12 - 70.4210e12 = // 1329.5789e12GBP env.require(Balance(alice_, gbp(1'329'578'947'368'420))); - //// 25% on 56.3368e12ETH is paid in tr fee 56.3368e12*1.25 - ///= 70.4210e12ETH + /** + * / 25% on 56.3368e12ETH is paid in tr fee 56.3368e12*1.25 + * = 70.4210e12ETH + */ // 56.3368e12GBP is swapped in for 53.3322e12ETH BEAST_EXPECT(amm.expectBalances( gbp(1'056'336'842'105'264), eth(946'667'729'591'836), amm.tokens())); diff --git a/src/test/app/AMMExtended_test.cpp b/src/test/app/AMMExtended_test.cpp index a0a7d0fb15..bb532b361a 100644 --- a/src/test/app/AMMExtended_test.cpp +++ b/src/test/app/AMMExtended_test.cpp @@ -546,7 +546,7 @@ private: // 1 for each trust limit == 3 (alice_ < mtgox/amazon/bitstamp) + // 1 for payment == 4 auto const startingXrp = - XRP(100) + env.current()->fees().accountReserve(3) + env.current()->fees().base * 4; + XRP(100) + env.current()->fees().accountReserve(3, 1) + env.current()->fees().base * 4; env.fund(startingXrp, gw1, gw2, gw3, localAlice); env.fund(XRP(2'000), localBob); @@ -2420,8 +2420,10 @@ private: // 1,400 - 56.3368*1.25 = 1400 - 70.4210 = 1329.5789GBP BEAST_EXPECT( expectHolding(env, alice_, STAmount{GBP, UINT64_C(1'329'578947368421), -12})); - //// 25% on 56.3368EUR is paid in tr fee 56.3368*1.25 - ///= 70.4210EUR + /** + * / 25% on 56.3368EUR is paid in tr fee 56.3368*1.25 + * = 70.4210EUR + */ // 56.3368GBP is swapped in for 53.3322EUR BEAST_EXPECT(amm.expectBalances( STAmount{GBP, UINT64_C(1'056'336842105263), -12}, @@ -2435,8 +2437,10 @@ private: // 1,400 - 56.3368*1.25 = 1400 - 70.4210 = 1329.5789GBP BEAST_EXPECT( expectHolding(env, alice_, STAmount{GBP, UINT64_C(1'329'57894736842), -11})); - //// 25% on 56.3368EUR is paid in tr fee 56.3368*1.25 - ///= 70.4210EUR + /** + * / 25% on 56.3368EUR is paid in tr fee 56.3368*1.25 + * = 70.4210EUR + */ // 56.3368GBP is swapped in for 53.3322EUR BEAST_EXPECT(amm.expectBalances( STAmount{GBP, UINT64_C(1'056'336842105264), -12}, diff --git a/src/test/app/AMMMPT_test.cpp b/src/test/app/AMMMPT_test.cpp index 786d3eca76..f34d3022f9 100644 --- a/src/test/app/AMMMPT_test.cpp +++ b/src/test/app/AMMMPT_test.cpp @@ -1003,7 +1003,7 @@ private: // Insufficient reserve, XRP/MPT { - Env env(*this); + Env env(*this, features); auto const startingXrp = reserve(env, 4) + env.current()->fees().base * 4; env.fund(XRP(10'000), gw_); env.fund(XRP(10'000), alice_); diff --git a/src/test/app/AccountSet_test.cpp b/src/test/app/AccountSet_test.cpp index 362113aec2..d94fb66e50 100644 --- a/src/test/app/AccountSet_test.cpp +++ b/src/test/app/AccountSet_test.cpp @@ -1,4 +1,3 @@ - #include #include #include @@ -453,7 +452,7 @@ public: // We'll insert a replacement for the account root // with the higher (currently invalid) transfer rate. - auto replacement = std::make_shared(*sle, sle->key()); + auto replacement = std::make_shared(*sle); (*replacement)[sfTransferRate] = static_cast(transferRate * QUALITY_ONE); view.rawReplace(replacement); diff --git a/src/test/app/CheckMPT_test.cpp b/src/test/app/CheckMPT_test.cpp index 861a115fc9..6370be7b6f 100644 --- a/src/test/app/CheckMPT_test.cpp +++ b/src/test/app/CheckMPT_test.cpp @@ -409,7 +409,7 @@ class CheckMPT_test : public beast::unit_test::Suite // Insufficient reserve. Account const cheri{"cheri"}; - env.fund(env.current()->fees().accountReserve(1) - drops(1), cheri); + env.fund(env.current()->fees().accountReserve(1, 1) - drops(1), cheri); env(check::create(cheri, bob, usd(50)), Fee(drops(env.current()->fees().base)), @@ -1411,7 +1411,8 @@ class CheckMPT_test : public beast::unit_test::Suite return acct.id(); } - /** Create MPTTester if it doesn't exist for the given MPT. + /** + * Create MPTTester if it doesn't exist for the given MPT. * Increment owners if created since it creates MPTokenIssuance */ MPT diff --git a/src/test/app/Check_test.cpp b/src/test/app/Check_test.cpp index 3ad1e3fa4e..b19a9f6894 100644 --- a/src/test/app/Check_test.cpp +++ b/src/test/app/Check_test.cpp @@ -470,7 +470,7 @@ class Check_test : public beast::unit_test::Suite // Insufficient reserve. Account const cheri{"cheri"}; - env.fund(env.current()->fees().accountReserve(1) - drops(1), cheri); + env.fund(env.current()->fees().accountReserve(1, 1) - drops(1), cheri); env.close(); env(check::create(cheri, bob, usd(50)), diff --git a/src/test/app/Credentials_test.cpp b/src/test/app/Credentials_test.cpp index 456a53bc01..1f6ec012c8 100644 --- a/src/test/app/Credentials_test.cpp +++ b/src/test/app/Credentials_test.cpp @@ -638,8 +638,8 @@ struct Credentials_test : public beast::unit_test::Suite { Env env{*this, features}; - env.fund(drops(env.current()->fees().accountReserve(1)), issuer); - env.fund(drops(env.current()->fees().accountReserve(0)), subject); + env.fund(drops(env.current()->fees().accountReserve(1, 1)), issuer); + env.fund(drops(env.current()->fees().accountReserve(0, 1)), subject); env.close(); { diff --git a/src/test/app/Delegate_test.cpp b/src/test/app/Delegate_test.cpp index 6f1d2ce7de..257ed33619 100644 --- a/src/test/app/Delegate_test.cpp +++ b/src/test/app/Delegate_test.cpp @@ -275,7 +275,7 @@ class Delegate_test : public beast::unit_test::Suite Account const bob{"bob"}; auto const txFee = env.current()->fees().base; - env.fund(env.current()->fees().accountReserve(0) + txFee, alice); + env.fund(env.current()->fees().accountReserve(0, 1) + txFee, alice); env.fund(XRP(100000), bob); env.close(); @@ -292,7 +292,7 @@ class Delegate_test : public beast::unit_test::Suite auto const txFee = env.current()->fees().base; - env.fund(env.current()->fees().accountReserve(1) + (txFee * 4), alice); + env.fund(env.current()->fees().accountReserve(1, 1) + (txFee * 4), alice); env.fund(XRP(100000), bob, carol); env.close(); @@ -318,8 +318,8 @@ class Delegate_test : public beast::unit_test::Suite Account const alice{"alice"}; Account const bob{"bob"}; - env.fund(drops(env.current()->fees().accountReserve(1)), alice); - env.fund(drops(env.current()->fees().accountReserve(2)), bob); + env.fund(drops(env.current()->fees().accountReserve(1, 1)), alice); + env.fund(drops(env.current()->fees().accountReserve(2, 1)), bob); env.close(); // alice gives bob permission @@ -422,7 +422,7 @@ class Delegate_test : public beast::unit_test::Suite Account const carol{"carol"}; auto const baseFee = env.current()->fees().base; - auto const reserve = env.current()->fees().accountReserve(1); + auto const reserve = env.current()->fees().accountReserve(1, 1); auto const paymentAmount = XRP(1); auto const highFee = reserve + baseFee; BEAST_EXPECT(highFee > reserve); @@ -488,9 +488,9 @@ class Delegate_test : public beast::unit_test::Suite Account const carol{"carol"}; auto const baseFee = env.current()->fees().base; - auto const baseReserve = env.current()->fees().accountReserve(0); + auto const baseReserve = env.current()->fees().accountReserve(0, 1); - env.fund(env.current()->fees().accountReserve(1) + baseFee + XRP(1), alice); + env.fund(env.current()->fees().accountReserve(1, 1) + baseFee + XRP(1), alice); env.fund(baseReserve, bob); env.fund(XRP(1000), carol); env.close(); @@ -523,7 +523,7 @@ class Delegate_test : public beast::unit_test::Suite Account const carol{"carol"}; auto const baseFee = env.current()->fees().base; - auto const reserve = env.current()->fees().accountReserve(1); + auto const reserve = env.current()->fees().accountReserve(1, 1); // Alice is funded with (reserve + baseFee): after DelegateSet she has // exactly 'reserve', which is insufficient to send XRP(10) while keeping @@ -2612,7 +2612,9 @@ class Delegate_test : public beast::unit_test::Suite {"CredentialDelete", featureCredentials}, {"NFTokenModify", featureDynamicNFT}, {"PermissionedDomainSet", featurePermissionedDomains}, - {"PermissionedDomainDelete", featurePermissionedDomains}}; + {"PermissionedDomainDelete", featurePermissionedDomains}, + {"SponsorshipSet", featureSponsor}, + }; // Can not delegate tx if any required feature disabled. { @@ -2747,7 +2749,7 @@ class Delegate_test : public beast::unit_test::Suite // DO NOT modify expectedDelegableCount unless all scenarios, including // edge cases, have been fully tested and verified. // ==================================================================== - std::size_t const expectedDelegableCount = 56; + std::size_t const expectedDelegableCount = 57; BEAST_EXPECTS( delegableCount == expectedDelegableCount, diff --git a/src/test/app/DepositAuth_test.cpp b/src/test/app/DepositAuth_test.cpp index 3496e67b54..c75bdeaf3a 100644 --- a/src/test/app/DepositAuth_test.cpp +++ b/src/test/app/DepositAuth_test.cpp @@ -51,7 +51,7 @@ namespace xrpl::test { static XRPAmount reserve(jtx::Env& env, std::uint32_t count) { - return env.current()->fees().accountReserve(count); + return env.current()->fees().accountReserve(count, 1); } // Helper function that returns true if acct has the lsfDepositAuth flag set. @@ -1025,7 +1025,7 @@ struct DepositPreauth_test : public beast::unit_test::Suite { // not enough reserve Account const john{"john"}; - env.fund(env.current()->fees().accountReserve(0), john); + env.fund(env.current()->fees().accountReserve(0, 1), john); env.close(); auto jv = deposit::authCredentials(john, {{.issuer = issuer, .credType = credType}}); diff --git a/src/test/app/EscrowToken_test.cpp b/src/test/app/EscrowToken_test.cpp index 666ee498bd..4015f5ddc8 100644 --- a/src/test/app/EscrowToken_test.cpp +++ b/src/test/app/EscrowToken_test.cpp @@ -937,7 +937,7 @@ struct EscrowToken_test : public beast::unit_test::Suite auto const expectedResult = env.current()->rules().enabled(fixCleanup3_2_0) ? Ter(tesSUCCESS) - : Ter(tefEXCEPTION); + : Ter(tefINTERNAL); env(escrow::cancel(alice, alice, seq), Fee(baseFee), expectedResult); env.close(); diff --git a/src/test/app/FlowMPT_test.cpp b/src/test/app/FlowMPT_test.cpp index 19bc25fae7..ad3edd49e9 100644 --- a/src/test/app/FlowMPT_test.cpp +++ b/src/test/app/FlowMPT_test.cpp @@ -798,7 +798,7 @@ struct FlowMPT_test : public beast::unit_test::Suite static XRPAmount reserve(jtx::Env& env, std::uint32_t count) { - return env.current()->fees().accountReserve(count); + return env.current()->fees().accountReserve(count, 1); } // Helper function that returns the Offers on an account. diff --git a/src/test/app/Flow_test.cpp b/src/test/app/Flow_test.cpp index 72e39ab890..8d5162394e 100644 --- a/src/test/app/Flow_test.cpp +++ b/src/test/app/Flow_test.cpp @@ -706,7 +706,7 @@ struct Flow_test : public beast::unit_test::Suite static XRPAmount reserve(jtx::Env& env, std::uint32_t count) { - return env.current()->fees().accountReserve(count); + return env.current()->fees().accountReserve(count, 1); } // Helper function that returns the Offers on an account. diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index 79fdbb54ed..461a151d8e 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -101,7 +101,8 @@ class Invariants_test : public beast::unit_test::Suite return xrpl::test::jtx::testableAmendments() | fixCleanup3_1_3 | fixCleanup3_2_0; } - /** Run a specific test case to put the ledger into a state that will be + /** + * Run a specific test case to put the ledger into a state that will be * detected by an invariant. Simulates the actions of a transaction that * would violate an invariant. * @@ -337,7 +338,72 @@ class Invariants_test : public beast::unit_test::Suite // check. sleA1->at(sfBalance) = beast::kZero; BEAST_EXPECT(sleA1->at(sfOwnerCount) == 0); - adjustOwnerCount(ac.view(), sleA1, 1, ac.journal); + increaseOwnerCount(ac.view(), sleA1, {}, 1, ac.journal); + + ac.view().erase(sleA1); + + return true; + }, + XRPAmount{}, + STTx{ttACCOUNT_DELETE, [](STObject& tx) {}}); + + doInvariantCheck( + {{"account deletion left behind a sponsorship field"}}, + [&](Account const& a1, Account const& a2, ApplyContext& ac) { + auto const sleA1 = ac.view().peek(keylet::account(a1.id())); + if (!sleA1) + return false; + sleA1->at(sfBalance) = beast::kZero; + sleA1->setFieldU32(sfSponsoredOwnerCount, 1); + + ac.view().erase(sleA1); + + return true; + }, + XRPAmount{}, + STTx{ttACCOUNT_DELETE, [](STObject& tx) {}}); + + doInvariantCheck( + {{"account deletion left behind a sponsorship field"}}, + [&](Account const& a1, Account const& a2, ApplyContext& ac) { + auto const sleA1 = ac.view().peek(keylet::account(a1.id())); + if (!sleA1) + return false; + sleA1->at(sfBalance) = beast::kZero; + sleA1->setFieldU32(sfSponsoringOwnerCount, 1); + + ac.view().erase(sleA1); + + return true; + }, + XRPAmount{}, + STTx{ttACCOUNT_DELETE, [](STObject& tx) {}}); + + doInvariantCheck( + {{"account deletion left behind a sponsorship field"}}, + [&](Account const& a1, Account const& a2, ApplyContext& ac) { + auto const a1Id = a1.id(); + auto const sleA1 = ac.view().peek(keylet::account(a1Id)); + if (!sleA1) + return false; + sleA1->at(sfBalance) = beast::kZero; + sleA1->setFieldU32(sfSponsoringAccountCount, 1); + + ac.view().erase(sleA1); + + return true; + }, + XRPAmount{}, + STTx{ttACCOUNT_DELETE, [](STObject& tx) {}}); + + doInvariantCheck( + {{"account deletion left behind a sponsorship field"}}, + [&](Account const& a1, Account const& a2, ApplyContext& ac) { + auto const sleA1 = ac.view().peek(keylet::account(a1.id())); + if (!sleA1) + return false; + sleA1->at(sfBalance) = beast::kZero; + sleA1->setAccountID(sfSponsor, a2.id()); ac.view().erase(sleA1); @@ -1771,6 +1837,7 @@ class Invariants_test : public beast::unit_test::Suite "pseudo-account sequence changed" "pseudo-account flags are not set" "pseudo-account has a regular key" + "pseudo-account has a sponsorship field" */ struct Mod { @@ -1798,6 +1865,22 @@ class Invariants_test : public beast::unit_test::Suite .expectedFailure = "pseudo-account has a regular key", .func = [](SLE::pointer& sle) { sle->at(sfRegularKey) = Account("regular").id(); }, }, + { + .expectedFailure = "pseudo-account has a sponsorship field", + .func = [](SLE::pointer& sle) { sle->at(sfSponsoredOwnerCount) = 1; }, + }, + { + .expectedFailure = "pseudo-account has a sponsorship field", + .func = [](SLE::pointer& sle) { sle->at(sfSponsoringOwnerCount) = 1; }, + }, + { + .expectedFailure = "pseudo-account has a sponsorship field", + .func = [](SLE::pointer& sle) { sle->at(sfSponsoringAccountCount) = 1; }, + }, + { + .expectedFailure = "pseudo-account has a sponsorship field", + .func = [](SLE::pointer& sle) { sle->at(sfSponsor) = Account("sponsor").id(); }, + }, }); for (auto const& mod : mods) @@ -4948,11 +5031,10 @@ class Invariants_test : public beast::unit_test::Suite std::vector values; }; - for (auto const mantissaScale : { - MantissaRange::MantissaScale::LargeLegacy, - MantissaRange::MantissaScale::Large, - }) + for (auto const mantissaScale : MantissaRange::getAllScales()) { + if (mantissaScale == MantissaRange::MantissaScale::Small) + continue; NumberMantissaScaleGuard const g{mantissaScale}; auto makeDelta = [&vaultAsset](Number const& n) -> ValidVault::DeltaInfo { @@ -5065,6 +5147,111 @@ class Invariants_test : public beast::unit_test::Suite } } + void + testSponsorship() + { + using namespace test::jtx; + using namespace std::string_literals; + testcase("Sponsorship"); + { + auto const expectMessage = + "SponsoredOwnerCount does not equal SponsoringOwnerCount delta."; + + doInvariantCheck( + {{expectMessage}}, [&](Account const& a1, Account const& a2, ApplyContext& ac) { + auto const sle = ac.view().peek(keylet::account(a1.id())); + if (!sle) + return false; + sle->setFieldU32(sfSponsoredOwnerCount, 1); + ac.view().update(sle); + return true; + }); + + doInvariantCheck( + {{expectMessage}}, [&](Account const& a1, Account const& a2, ApplyContext& ac) { + auto const sle = ac.view().peek(keylet::account(a1.id())); + if (!sle) + return false; + sle->setFieldU32(sfSponsoringOwnerCount, 1); + ac.view().update(sle); + return true; + }); + } + + { + auto const expectMessage = + "OwnerCount must be greater than or equal to SponsoredOwnerCount."; + + doInvariantCheck( + {{expectMessage}}, [&](Account const& a1, Account const& a2, ApplyContext& ac) { + auto const sle = ac.view().peek(keylet::account(a1.id())); + if (!sle) + return false; + sle->setFieldU32(sfOwnerCount, 0); + sle->setFieldU32(sfSponsoredOwnerCount, 1); + ac.view().update(sle); + + auto const sle2 = ac.view().peek(keylet::account(a2.id())); + if (!sle2) + return false; + sle2->setFieldU32(sfSponsoringOwnerCount, 1); + ac.view().update(sle2); + return true; + }); + } + + { + auto const expectMessage = + "SponsoredObjectOwnerCount does not equal SponsoredOwnerCount delta."; + uint256 checkID; + + doInvariantCheck( + {{expectMessage}}, + [&](Account const&, Account const& a2, ApplyContext& ac) { + auto const check = ac.view().peek(keylet::check(checkID)); + if (!check) + return false; + check->setAccountID(sfSponsor, a2.id()); + ac.view().update(check); + return true; + }, + XRPAmount{}, + STTx{ttACCOUNT_SET, [](STObject&) {}}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}, + [&checkID](Account const& a1, Account const& a2, Env& env) { + checkID = keylet::check(a1.id(), env.seq(a1)).key; + env(check::create(a1, a2, XRP(1))); + return true; + }); + } + + { + auto const expectMessage = + "Invariant failed: Net delta of SponsoringAccountCount does " + "not match net delta of sfSponsor presence."; + + doInvariantCheck( + {{expectMessage}}, [&](Account const& a1, Account const& a2, ApplyContext& ac) { + auto const sle = ac.view().peek(keylet::account(a1.id())); + if (!sle) + return false; + sle->setFieldU32(sfSponsoringAccountCount, 1); + ac.view().update(sle); + return true; + }); + + doInvariantCheck( + {{expectMessage}}, [&](Account const& a1, Account const& a2, ApplyContext& ac) { + auto const sle = ac.view().peek(keylet::account(a1.id())); + if (!sle) + return false; + sle->setAccountID(sfSponsor, a2.id()); + ac.view().update(sle); + return true; + }); + } + } + void testObjectHasPseudoAccount() { @@ -5467,6 +5654,7 @@ public: testVaultComputeCoarsestScale(); testAMM(); testObjectHasPseudoAccount(); + testSponsorship(); } }; diff --git a/src/test/app/LedgerHistory_test.cpp b/src/test/app/LedgerHistory_test.cpp index 3d0e546678..f8688899e0 100644 --- a/src/test/app/LedgerHistory_test.cpp +++ b/src/test/app/LedgerHistory_test.cpp @@ -29,13 +29,13 @@ namespace xrpl::test { class LedgerHistory_test : public beast::unit_test::Suite { public: - /** Generate a new ledger by hand, applying a specific close time offset - and optionally inserting a transaction. - - If prev is nullptr, then the genesis ledger is made and no offset or - transaction is applied. - - */ + /** + * Generate a new ledger by hand, applying a specific close time offset + * and optionally inserting a transaction. + * + * If prev is nullptr, then the genesis ledger is made and no offset or + * transaction is applied. + */ static std::shared_ptr makeLedger( std::shared_ptr const& prev, diff --git a/src/test/app/LoanBroker_test.cpp b/src/test/app/LoanBroker_test.cpp index 2a4de18a9c..b0b0924c6e 100644 --- a/src/test/app/LoanBroker_test.cpp +++ b/src/test/app/LoanBroker_test.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -1078,7 +1079,7 @@ class LoanBroker_test : public beast::unit_test::Suite } auto const amt = - env.balance(alice) - env.current()->fees().accountReserve(env.ownerCount(alice)); + env.balance(alice) - accountReserve(*env.current(), alice.id(), env.journal); env(pay(alice, issuer, amt)); // preclaim:: tecINSUFFICIENT_RESERVE diff --git a/src/test/app/Loan_test.cpp b/src/test/app/Loan_test.cpp index 3e62af48ff..371fcae54f 100644 --- a/src/test/app/Loan_test.cpp +++ b/src/test/app/Loan_test.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -36,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -322,7 +324,8 @@ protected: TenthBips32 const interestRate{}; }; - /** Helper class to compare the expected state of a loan and loan broker + /** + * Helper class to compare the expected state of a loan and loan broker * against the data in the ledger. */ struct VerifyLoanStatus @@ -342,7 +345,8 @@ protected: { } - /** Checks the expected broker state against the ledger + /** + * Checks the expected broker state against the ledger */ void checkBroker( @@ -413,7 +417,9 @@ protected: } } - /** Checks both the loan and broker expect states against the ledger */ + /** + * Checks both the loan and broker expect states against the ledger + */ void operator()( std::uint32_t previousPaymentDate, @@ -473,7 +479,9 @@ protected: } } - /** Checks both the loan and broker expect states against the ledger */ + /** + * Checks both the loan and broker expect states against the ledger + */ void operator()(LoanState const& state) const { @@ -539,7 +547,9 @@ protected: return {asset, keylet, vaultKeylet, params}; } - /// Get the state without checking anything + /** + * Get the state without checking anything + */ LoanState getCurrentState(jtx::Env const& env, BrokerInfo const& broker, Keylet const& loanKeylet) { @@ -567,8 +577,10 @@ protected: return LoanState{}; } - /// Get the state and check the values against the parameters used in - /// `lifecycle` + /** + * Get the state and check the values against the parameters used in + * `lifecycle` + */ LoanState getCurrentState( jtx::Env const& env, @@ -671,11 +683,11 @@ protected: case AssetType::MPT: { // Enough to cover initial fees if (!env.le(keylet::account(issuer))) - env.fund(env.current()->fees().accountReserve(10) * 10, issuer); + env.fund(env.current()->fees().accountReserve(10, 1) * 10, issuer); if (!env.le(keylet::account(lender))) - env.fund(env.current()->fees().accountReserve(10) * 10, noripple(lender)); + env.fund(env.current()->fees().accountReserve(10, 1) * 10, noripple(lender)); if (!env.le(keylet::account(borrower))) - env.fund(env.current()->fees().accountReserve(10) * 10, noripple(borrower)); + env.fund(env.current()->fees().accountReserve(10, 1) * 10, noripple(borrower)); MPTTester mptt{env, issuer, kMptInitNoFund}; mptt.create({.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock}); @@ -760,11 +772,11 @@ protected: using namespace jtx; // Enough to cover initial fees - env.fund(env.current()->fees().accountReserve(10) * 10, issuer); + env.fund(env.current()->fees().accountReserve(10, 1) * 10, issuer); if (lender != issuer) - env.fund(env.current()->fees().accountReserve(10) * 10, noripple(lender)); + env.fund(env.current()->fees().accountReserve(10, 1) * 10, noripple(lender)); if (borrower != issuer && borrower != lender) - env.fund(env.current()->fees().accountReserve(10) * 10, noripple(borrower)); + env.fund(env.current()->fees().accountReserve(10, 1) * 10, noripple(borrower)); describeLoan(env, brokerParams, loanParams, assetType, issuer, lender, borrower); @@ -841,11 +853,10 @@ protected: // Add extra for transaction fees and reserves, if appropriate, or a // tiny amount for the extra paid in each transaction auto const totalNeeded = state.totalValue + (serviceFee * state.paymentRemaining) + - (broker.asset.native() - ? Number( - baseFee * state.paymentRemaining + - env.current()->fees().accountReserve(env.ownerCount(borrower))) - : broker.asset(15).number()); + (broker.asset.native() ? Number( + baseFee * state.paymentRemaining + + accountReserve(*env.current(), borrower.id(), env.journal)) + : broker.asset(15).number()); auto const shortage = totalNeeded - borrowerBalance.number(); @@ -1241,7 +1252,8 @@ protected: PaymentParameters{.showStepBalances = true}); } - /** Runs through the complete lifecycle of a loan + /** + * Runs through the complete lifecycle of a loan * * 1. Create a loan. * 2. Test a bunch of transaction failure conditions. @@ -1559,7 +1571,8 @@ protected: return "Unknown"; } - /** Wrapper to run a series of lifecycle tests for a given asset and loan + /** + * Wrapper to run a series of lifecycle tests for a given asset and loan * amount * * Will be used in the future to vary the loan parameters. For now, it is @@ -3071,7 +3084,7 @@ protected: auto const [acctReserve, incReserve] = [this]() -> std::pair { Env const env{*this, testableAmendments()}; return { - env.current()->fees().accountReserve(0).drops() / kDropsPerXrp.drops(), + env.current()->fees().accountReserve(0, 1).drops() / kDropsPerXrp.drops(), env.current()->fees().increment.drops() / kDropsPerXrp.drops()}; }(); @@ -4419,11 +4432,12 @@ protected: Account const lender{"lender"}; Account const issuer{"issuer"}; Account const borrower{"borrower"}; + Account const sponsor{"sponsor"}; auto const iou = issuer["IOU"]; auto testWrapper = [&](auto&& test) { Env env(*this); - env.fund(XRP(1'000), lender, issuer, borrower); + env.fund(XRP(1'000), lender, issuer, borrower, sponsor); env(trust(lender, iou(10'000'000))); env(pay(issuer, lender, iou(5'000'000))); BrokerInfo const brokerInfo{createVaultAndBroker(env, issuer["IOU"], lender)}; @@ -4438,6 +4452,15 @@ protected: BrokerInfo const& brokerInfo, jtx::Fee const& loanSetFee, Number const& debtMaximumRequest) { + for (auto const sponsorFlags : {spfSponsorReserve, spfSponsorReserve | spfSponsorFee}) + { + env(set(borrower, brokerInfo.brokerID, debtMaximumRequest), + sponsor::As(sponsor, sponsorFlags), + Sig(sfCounterpartySignature, lender), + loanSetFee, + Ter(temINVALID_FLAG)); + } + // first temBAD_SIGNER: TODO // invalid grace period { @@ -4528,8 +4551,8 @@ protected: BrokerInfo const& brokerInfo, jtx::Fee const& loanSetFee, Number const& debtMaximumRequest) { - auto const amt = env.balance(borrower) - - env.current()->fees().accountReserve(env.ownerCount(borrower)); + auto const amt = + env.balance(borrower) - accountReserve(*env.current(), borrower.id(), env.journal); env(pay(borrower, issuer, amt)); // tecINSUFFICIENT_RESERVE diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index bbf1c54ab1..7543db7364 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -2108,6 +2108,15 @@ class MPToken_test : public beast::unit_test::Suite reward = STAmount{sfSignatureReward, usd(10)}; minAmount = STAmount{sfMinAccountCreateAmount, mpt}; } + // SponsorshipSet + { + json::Value jv; + jv[jss::TransactionType] = jss::SponsorshipSet; + jv[jss::Account] = alice.human(); + jv[sfSponsee.fieldName] = carol.human(); + jv[sfFeeAmount.fieldName] = mpt.getJson(JsonOptions::Values::None); + test(jv, sfFeeAmount.fieldName.c_str()); + } } BEAST_EXPECT(txWithAmounts.empty()); } @@ -7563,7 +7572,7 @@ class MPToken_test : public beast::unit_test::Suite void testFixDoubleOwnerCount(FeatureBitset all) { - testcase("Fix Double adjustOwnerCount in AMMWithdraw"); + testcase("Fix Double OwnerCount in AMMWithdraw"); using namespace jtx; diff --git a/src/test/app/NFToken_test.cpp b/src/test/app/NFToken_test.cpp index 844bb4ee87..acd54ae26a 100644 --- a/src/test/app/NFToken_test.cpp +++ b/src/test/app/NFToken_test.cpp @@ -452,7 +452,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::Suite // Just for sanity's sake we'll check that the current value // of sfMintedNFTokens matches what we expect. - auto replacement = std::make_shared(*sle, sle->key()); + auto replacement = std::make_shared(*sle); if (replacement->getFieldU32(sfMintedNFTokens) != 1) return false; // Unexpected test conditions. diff --git a/src/test/app/OfferMPT_test.cpp b/src/test/app/OfferMPT_test.cpp index 52e6652327..0eded72f86 100644 --- a/src/test/app/OfferMPT_test.cpp +++ b/src/test/app/OfferMPT_test.cpp @@ -60,7 +60,7 @@ class OfferMPT_test : public beast::unit_test::Suite static XRPAmount reserve(jtx::Env& env, std::uint32_t count) { - return env.current()->fees().accountReserve(count); + return env.current()->fees().accountReserve(count, 1); } static std::uint32_t @@ -1795,7 +1795,8 @@ public: // 1 for each trust limit == 3 (alice < mtgox/amazon/bitstamp) + // 1 for payment == 4 auto const base = env.current()->fees().base; - auto const startingXrp = XRP(100) + env.current()->fees().accountReserve(3) + base * 4; + auto const startingXrp = + XRP(100) + env.current()->fees().accountReserve(3, 1) + base * 4; env.fund(startingXrp, gw1, gw2, gw3, alice, bob); env.close(); @@ -1815,7 +1816,8 @@ public: env(offer(alice, usD1(200), XRP(200))); BEAST_EXPECT(env.balance(alice, usD1) == usD1(100)); - BEAST_EXPECT(env.balance(alice) == STAmount(env.current()->fees().accountReserve(3))); + BEAST_EXPECT( + env.balance(alice) == STAmount(env.current()->fees().accountReserve(3, 1))); BEAST_EXPECT(env.balance(bob, usD1) == usD1(400)); }; @@ -1866,7 +1868,7 @@ public: auto const bob = Account{"bob"}; auto const startingXrp = - XRP(100) + env.current()->fees().accountReserve(1) + env.current()->fees().base * 2; + XRP(100) + env.current()->fees().accountReserve(1, 1) + env.current()->fees().base * 2; env.fund(startingXrp, gw, alice, bob); @@ -1885,7 +1887,7 @@ public: jrr = ledgerEntryRoot(env, alice); BEAST_EXPECT( jrr[jss::node][sfBalance.fieldName] == - STAmount(env.current()->fees().accountReserve(1)).getText()); + STAmount(env.current()->fees().accountReserve(1, 1)).getText()); jrr = ledgerEntryMPT(env, bob, usd); BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "400"); @@ -1905,7 +1907,7 @@ public: auto const bob = Account{"bob"}; auto const startingXrp = - XRP(100) + env.current()->fees().accountReserve(1) + env.current()->fees().base * 2; + XRP(100) + env.current()->fees().accountReserve(1, 1) + env.current()->fees().base * 2; env.fund(startingXrp, gw, alice, bob); @@ -1926,7 +1928,7 @@ public: jrr = ledgerEntryRoot(env, alice); BEAST_EXPECT( jrr[jss::node][sfBalance.fieldName] == - STAmount(env.current()->fees().accountReserve(1)).getText()); + STAmount(env.current()->fees().accountReserve(1, 1)).getText()); jrr = ledgerEntryMPT(env, bob, usd); BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "300"); @@ -1947,7 +1949,7 @@ public: auto const base = env.current()->fees().base; auto const startingXrp = - XRP(100.1) + env.current()->fees().accountReserve(1) + base * 2; + XRP(100.1) + env.current()->fees().accountReserve(1, 1) + base * 2; env.fund(startingXrp, gw, alice, bob); env.close(); diff --git a/src/test/app/Offer_test.cpp b/src/test/app/Offer_test.cpp index 2724a6474b..7fc7161e36 100644 --- a/src/test/app/Offer_test.cpp +++ b/src/test/app/Offer_test.cpp @@ -60,7 +60,7 @@ class OfferBaseUtil_test : public beast::unit_test::Suite static XRPAmount reserve(jtx::Env& env, std::uint32_t count) { - return env.current()->fees().accountReserve(count); + return env.current()->fees().accountReserve(count, 1); } static std::uint32_t @@ -1962,7 +1962,7 @@ public: // 1 for each trust limit == 3 (alice < mtgox/amazon/bitstamp) + // 1 for payment == 4 auto const startingXrp = - XRP(100) + env.current()->fees().accountReserve(3) + env.current()->fees().base * 4; + XRP(100) + env.current()->fees().accountReserve(3, 1) + env.current()->fees().base * 4; env.fund(startingXrp, gw1, gw2, gw3, alice, bob); env.close(); @@ -1985,7 +1985,7 @@ public: jrr = ledgerEntryRoot(env, alice); BEAST_EXPECT( jrr[jss::node][sfBalance.fieldName] == - STAmount(env.current()->fees().accountReserve(3)).getText()); + STAmount(env.current()->fees().accountReserve(3, 1)).getText()); jrr = ledgerEntryState(env, bob, gw1, "USD"); BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-400"); @@ -2045,7 +2045,7 @@ public: auto const usd = gw["USD"]; auto const startingXrp = - XRP(100) + env.current()->fees().accountReserve(1) + env.current()->fees().base * 2; + XRP(100) + env.current()->fees().accountReserve(1, 1) + env.current()->fees().base * 2; env.fund(startingXrp, gw, alice, bob); env.close(); @@ -2066,7 +2066,7 @@ public: jrr = ledgerEntryRoot(env, alice); BEAST_EXPECT( jrr[jss::node][sfBalance.fieldName] == - STAmount(env.current()->fees().accountReserve(1)).getText()); + STAmount(env.current()->fees().accountReserve(1, 1)).getText()); jrr = ledgerEntryState(env, bob, gw, "USD"); BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-400"); @@ -2087,7 +2087,7 @@ public: auto const usd = gw["USD"]; auto const startingXrp = - XRP(100) + env.current()->fees().accountReserve(1) + env.current()->fees().base * 2; + XRP(100) + env.current()->fees().accountReserve(1, 1) + env.current()->fees().base * 2; env.fund(startingXrp, gw, alice, bob); env.close(); @@ -2110,7 +2110,7 @@ public: jrr = ledgerEntryRoot(env, alice); BEAST_EXPECT( jrr[jss::node][sfBalance.fieldName] == - STAmount(env.current()->fees().accountReserve(1)).getText()); + STAmount(env.current()->fees().accountReserve(1, 1)).getText()); jrr = ledgerEntryState(env, bob, gw, "USD"); BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-300"); @@ -2131,8 +2131,8 @@ public: auto const xts = gw["XTS"]; auto const xxx = gw["XXX"]; - auto const startingXrp = - XRP(100.1) + env.current()->fees().accountReserve(1) + env.current()->fees().base * 2; + auto const startingXrp = XRP(100.1) + env.current()->fees().accountReserve(1, 1) + + env.current()->fees().base * 2; env.fund(startingXrp, gw, alice, bob); env.close(); diff --git a/src/test/app/Oracle_test.cpp b/src/test/app/Oracle_test.cpp index 7e7f5e9bd0..b8d9fb32d1 100644 --- a/src/test/app/Oracle_test.cpp +++ b/src/test/app/Oracle_test.cpp @@ -60,7 +60,7 @@ private: // Insufficient reserve { Env env(*this); - env.fund(env.current()->fees().accountReserve(0), owner); + env.fund(env.current()->fees().accountReserve(0, 1), owner); Oracle const oracle( env, {.owner = owner, @@ -71,7 +71,7 @@ private: { Env env(*this); env.fund( - env.current()->fees().accountReserve(1) + env.current()->fees().base * 2, owner); + env.current()->fees().accountReserve(1, 1) + env.current()->fees().base * 2, owner); Oracle oracle( env, {.owner = owner, .fee = static_cast(env.current()->fees().base.drops())}); BEAST_EXPECT(oracle.exists()); @@ -640,7 +640,7 @@ private: Env env(*this); auto const baseFee = static_cast(env.current()->fees().base.drops()); env.fund( - env.current()->fees().accountReserve(1) + env.current()->fees().base * 2, owner); + env.current()->fees().accountReserve(1, 1) + env.current()->fees().base * 2, owner); Oracle oracle(env, {.owner = owner, .fee = baseFee}); oracle.set(UpdateArg{.series = {{"XRP", "USD", 742, 2}}, .fee = baseFee}); } diff --git a/src/test/app/PayStrand_test.cpp b/src/test/app/PayStrand_test.cpp index ddbcfe8b70..967f84f275 100644 --- a/src/test/app/PayStrand_test.cpp +++ b/src/test/app/PayStrand_test.cpp @@ -40,7 +40,6 @@ #include #include #include -#include #include #include #include @@ -309,31 +308,26 @@ struct ExistingElementPool currencyNames.clear(); currencyNames.reserve(numCur); - static constexpr size_t kBufSize = 32; - char buf[kBufSize]; - for (size_t id = 0; id < numAct; ++id) - { - snprintf(buf, kBufSize, "A%zu", id); - accounts.emplace_back(buf); - } + accounts.emplace_back("A" + std::to_string(id)); for (size_t id = 0; id < numCur; ++id) { + std::string name; if (id < 10) { - snprintf(buf, kBufSize, "CC%zu", id); + name = "CC" + std::to_string(id); } else if (id < 100) { - snprintf(buf, kBufSize, "C%zu", id); + name = "C" + std::to_string(id); } else { - snprintf(buf, kBufSize, "%zu", id); + name = std::to_string(id); } - currencies.emplace_back(toCurrency(buf)); - currencyNames.emplace_back(buf); + currencies.emplace_back(toCurrency(name)); + currencyNames.emplace_back(name); } for (auto const& a : accounts) diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp new file mode 100644 index 0000000000..f20aac68f9 --- /dev/null +++ b/src/test/app/Sponsor_test.cpp @@ -0,0 +1,5511 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { + +static STAmount +accountReserve(jtx::Env& env, std::uint32_t count = 1) +{ + return env.current()->fees().reserve * count; +} + +static STAmount +reserve(jtx::Env& env, std::uint32_t count) +{ + return env.current()->fees().accountReserve(count, 1); +} + +static void +adjustAccountXRPBalance(jtx::Env& env, jtx::Account const& account, STAmount const& balanceTo) +{ + using namespace test::jtx; + XRPL_ASSERT(isXRP(balanceTo), "adjustAccountXRPBalance: balanceTo must be XRP"); + auto const currentBalance = env.balance(account); + if (currentBalance == balanceTo) + return; + + auto const baseFee = env.current()->fees().base; + if (currentBalance > balanceTo) + { + env(pay(account, env.master, currentBalance - balanceTo), + Fee(XRP(1)), + sponsor::As(env.master, spfSponsorFee), + Sig(sfSponsorSignature, env.master)); + } + else + { + env(pay(env.master, account, balanceTo - currentBalance), Fee(baseFee)); + } + + env.close(); +} + +class Sponsor_test : public beast::unit_test::Suite +{ +public: + void + testDisabled() + { + testcase("Disabled"); + using namespace test::jtx; + Env env{*this, testableAmendments() - featureSponsor}; + Account const alice("alice"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, sponsor); + + // check Sponsor fields + auto const jt = noop(alice); + auto jt1 = jt; + jt1[sfSponsor.jsonName] = sponsor.human(); + env(jt1, Ter(temDISABLED)); + env(jt, Sig(sfSponsorSignature, sponsor), Ter(temDISABLED)); + + auto jt2 = jt; + jt2[sfSponsorFlags.jsonName] = spfSponsorFee | spfSponsorReserve; + env(jt2, Ter(temDISABLED)); + + // check Sponsor transactions + env(sponsor::transfer(alice, 0), Ter(temDISABLED)); + env(sponsor::set(sponsor, 0), Ter(temDISABLED)); + } + + void + testInvalidSponsorshipSet() + { + testcase("Invalid SponsorshipSet"); + using namespace test::jtx; + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + Account const noFunded("noFunded"); + Account const gw("gw"); + + auto const usd = gw["usd"]; + env.fund(XRP(10000), alice, sponsor, gw); + env.close(); + + // + // preflight + // + + // Invalid flags + { + env(sponsor::set(sponsor, ~tfSponsorshipSetMask - tfInnerBatchTxn), + sponsor::SponseeAcc(alice), + Ter(temINVALID_FLAG)); + + env(sponsor::set( + sponsor, + tfSponsorshipSetRequireSignForFee | tfSponsorshipClearRequireSignForFee), + sponsor::SponseeAcc(alice), + Ter(temINVALID_FLAG)); + + env(sponsor::set( + sponsor, + tfSponsorshipSetRequireSignForReserve | + tfSponsorshipClearRequireSignForReserve), + sponsor::SponseeAcc(alice), + Ter(temINVALID_FLAG)); + + for (auto flag : + {tfSponsorshipSetRequireSignForFee, + tfSponsorshipClearRequireSignForFee, + tfSponsorshipSetRequireSignForReserve, + tfSponsorshipClearRequireSignForReserve}) + { + env(sponsor::set(sponsor, tfDeleteObject | flag), + sponsor::SponseeAcc(alice), + Ter(temINVALID_FLAG)); + } + } + + // invalid SponsorAccount / Sponsee + // Account = Sponsor + env(sponsor::set(alice, tfDeleteObject), + sponsor::CounterpartySponsor(alice), + Ter(temMALFORMED)); + // Account = Sponsee + env(sponsor::set(alice, tfDeleteObject), sponsor::SponseeAcc(alice), Ter(temMALFORMED)); + // Both Sponsor and Sponsee are specified + env(sponsor::set(alice, 0), + sponsor::CounterpartySponsor(sponsor), + sponsor::SponseeAcc(alice), + Ter(temMALFORMED)); + + // Invalid feeAmount + for (auto const& amt : {XRP(-1), usd(1)}) + { + env(sponsor::set_fee(sponsor, 0, amt), sponsor::SponseeAcc(alice), Ter(temBAD_AMOUNT)); + } + // Invalid MaxFee + for (auto const& amt : {XRP(-1), usd(1)}) + { + env(sponsor::set_fee(sponsor, 0, XRP(1), amt), + sponsor::SponseeAcc(alice), + Ter(temBAD_AMOUNT)); + } + + // Invalid Delete operation + env(sponsor::set_reserve(sponsor, tfDeleteObject, 1), + sponsor::SponseeAcc(alice), + Ter(temMALFORMED)); + env(sponsor::set_fee(sponsor, tfDeleteObject, XRP(1)), + sponsor::SponseeAcc(alice), + Ter(temMALFORMED)); + env(sponsor::set_max_fee(sponsor, tfDeleteObject, XRP(1)), + sponsor::SponseeAcc(alice), + Ter(temMALFORMED)); + + // Invalid SponsorAccount with non-Delete operation + env(sponsor::set_reserve(sponsor, 0, 100), + sponsor::CounterpartySponsor(alice), + Ter(temMALFORMED)); + env(sponsor::set_fee(sponsor, 0, XRP(1), XRP(1)), + sponsor::CounterpartySponsor(alice), + Ter(temMALFORMED)); + + // + // preclaim + // + + // Invalid Sponsee + env(sponsor::set(sponsor, 0), sponsor::SponseeAcc(noFunded), Ter(tecNO_DST)); + env.close(); + + // Invalid Sponsor + env(sponsor::set(sponsor, tfDeleteObject), + sponsor::CounterpartySponsor(noFunded), + Ter(tecNO_DST)); + env.close(); + + // Invalid Delete operation (sponsorship not found) + env(sponsor::set(sponsor, tfDeleteObject), sponsor::SponseeAcc(alice), Ter(tecNO_ENTRY)); + env.close(); + + // insufficient balance to sponsor Fee + adjustAccountXRPBalance(env, sponsor, env.current()->fees().reserve); + env(sponsor::set_fee(sponsor, 0, XRP(4)), sponsor::SponseeAcc(alice), Ter(tecUNFUNDED)); + env.close(); + + // insufficient reserve to create sponsorship + adjustAccountXRPBalance(env, sponsor, XRP(100) + XRP(1) + reserve(env, 1) - drops(1)); + env(sponsor::set(sponsor, 0, 100, XRP(100)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tecUNFUNDED)); + env.close(); + + // FeeAmount + Fee > Balance + // Balance = 1000XRP, FeeAmount = 1001XRP + adjustAccountXRPBalance(env, sponsor, XRP(1000)); + env(sponsor::set_fee(sponsor, 0, XRP(1001)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tecUNFUNDED)); + env.close(); + // Balance = 1000XRP, FeeAmount = 999XRP, Fee=2XRP + adjustAccountXRPBalance(env, sponsor, XRP(1000)); + env(sponsor::set_fee(sponsor, 0, XRP(999)), + sponsor::SponseeAcc(alice), + Fee(XRP(2)), + Ter(tecUNFUNDED)); + env.close(); + + // create sponsor to use above tests + // need feeAmount(1000) + Fee(1) + reserve(~250) = ~1251 + adjustAccountXRPBalance(env, sponsor, XRP(1000) + XRP(1) + reserve(env, 1)); + env(sponsor::set(sponsor, 0, 100, XRP(1000)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tesSUCCESS)); + env.close(); + + // delta-based balance check + // After create: sponsor balance ~ 0, feeAmount = XRP(1000) + + // Decreasing feeAmount should succeed (refund, negative delta) + adjustAccountXRPBalance(env, sponsor, XRP(500)); + env(sponsor::set_fee(sponsor, 0, XRP(800)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tesSUCCESS)); + env.close(); + // balance was 500, delta = 800-1000 = -200 (refund), balance = 500+200-1 = 699 + + // Increasing feeAmount within delta budget should succeed + adjustAccountXRPBalance(env, sponsor, XRP(500)); + env(sponsor::set_fee(sponsor, 0, XRP(850)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tesSUCCESS)); + env.close(); + // balance was 500, delta = 850-800 = 50, balance = 500-50-1 = 449 + + // Increasing feeAmount where delta exceeds balance should fail + adjustAccountXRPBalance(env, sponsor, XRP(310)); + env(sponsor::set_fee(sponsor, 0, XRP(1200)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tecUNFUNDED)); + env.close(); + + // Increasing feeAmount to reach insufficient reserve + auto const currentFeeAmount = env.le(keylet::sponsorship(sponsor.id(), alice.id())) + ->getFieldAmount(sfFeeAmount) + .xrp(); + adjustAccountXRPBalance(env, sponsor, XRP(310)); + env(sponsor::set_fee(sponsor, 0, currentFeeAmount + XRP(309)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tecUNFUNDED)); + env.close(); + } + + void + testPseudoAccountSponsorship() + { + testcase("Pseudo account sponsorship"); + using namespace test::jtx; + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const gw("gw"); + Account const sp("sponsor"); + + Asset const asset = gw["IOU"].asset(); + + env.fund(XRP(1000000), alice, bob, gw, sp); + env.close(); + + // Create a vault to get a pseudo account + Vault const vault{env}; + auto [tx, keylet] = vault.create({.owner = alice, .asset = asset}); + env(tx); + env.close(); + + auto const vaultSle = env.le(keylet); + BEAST_EXPECT(vaultSle); + Account const pseudoAcc("vault", vaultSle->getAccountID(sfAccount)); + env.memoize(pseudoAcc); + + // Sponsee is a pseudo account -> tecNO_PERMISSION + env(sponsor::set(sp, 0, 100, XRP(100)), + sponsor::SponseeAcc(pseudoAcc), + Ter(tecNO_PERMISSION)); + env.close(); + + // Sponsor is a pseudo account -> tecNO_PERMISSION + // (submitted by bob with counterpartySponsor pointing to pseudo account) + env(sponsor::set(bob, tfDeleteObject), + sponsor::CounterpartySponsor(pseudoAcc), + Ter(tecNO_PERMISSION)); + env.close(); + } + + void + testSingleSigning() + { + testcase("Single signing"); + using namespace test::jtx; + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const sponsor("sponsor"); + Account const invalid("invalid"); + + env.fund(XRP(10000), alice, sponsor); + env.close(); + + // Signature doesn't exist + auto tx = noop(alice); + tx[sfSponsor.jsonName] = sponsor.human(); + tx[sfSponsorSignature.jsonName][sfSigningPubKey.jsonName] = strHex(sponsor.pk().slice()); + + env(tx, Fee(XRP(1)), sponsor::As(sponsor, spfSponsorReserve), Ter(telENV_RPC_FAILED)); + + // Invalid signature + tx[sfSponsorSignature.jsonName][sfTxnSignature.jsonName] = "DEADBEEF"; + env(tx, Fee(XRP(1)), sponsor::As(sponsor, spfSponsorReserve), Ter(telENV_RPC_FAILED)); + + // Signer account doesn't exist + env(noop(alice), + Fee(XRP(1)), + sponsor::As(invalid, spfSponsorReserve), + Sig(sfSponsorSignature, invalid), + Ter(terNO_ACCOUNT)); + + // Success + env(noop(alice), + Fee(XRP(1)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tesSUCCESS)); + } + + void + testMultiSigning() + { + testcase("Multi signing"); + using namespace test::jtx; + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + Account const signer1("signer1"); + Account const signer2("signer2"); + + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + env(signers(sponsor, 1, {{signer1, 1}, {signer2, 1}})); + env.close(); + + // Invalid signature + auto tx = noop(alice); + auto& signers1 = tx[sfSponsorSignature.jsonName][sfSigners.jsonName][0U][sfSigner.jsonName]; + signers1[sfAccount.jsonName] = signer1.human(); + signers1[sfSigningPubKey.jsonName] = strHex(signer1.pk().slice()); + signers1[sfTxnSignature.jsonName] = "DEADBEEF"; + env(tx, Fee(XRP(1)), sponsor::As(sponsor, spfSponsorReserve), Ter(telENV_RPC_FAILED)); + + // bob is not a multi-signing account. + env(noop(alice), + Fee(XRP(1)), + sponsor::As(bob, spfSponsorReserve), + Msig(sfSponsorSignature, {signer1}), + Ter(tefNOT_MULTI_SIGNING)); + + env(noop(alice), + Fee(XRP(1)), + sponsor::As(sponsor, spfSponsorReserve), + Msig(sfSponsorSignature, {signer1}), + Ter(tesSUCCESS)); + env.close(); + + env(signers(sponsor, 2, {{signer1, 1}, {signer2, 1}})); + env.close(); + + // test calculateBaseFee for multisigned sponsor + auto const baseFee = env.current()->fees().base; + env(noop(alice), + Fee(baseFee + 2 * baseFee - 1), + sponsor::As(sponsor, spfSponsorReserve), + Msig(sfSponsorSignature, {signer1, signer2}), + Ter(telINSUF_FEE_P)); + + env(noop(alice), + Fee(baseFee + 2 * baseFee), + sponsor::As(sponsor, spfSponsorReserve), + Msig(sfSponsorSignature, {signer1, signer2}), + Ter(tesSUCCESS)); + } + + void + testInvalidSponsorField() + { + testcase("Invalid Sponsor Field"); + using namespace test::jtx; + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const sponsor("sponsor"); + Account const noFunded("noFunded"); + env.fund(XRP(10000), alice, sponsor); + env.close(); + + // Invalid Sponsor Account (Account = Sponsor.Account) + env(noop(alice), sponsor::As(alice, spfSponsorFee), Ter(temMALFORMED)); + + // Invalid Sponsor Account + // (SponsorSignature is specified but Sponsor.Account is not specified) + env(noop(alice), Sig(sfSponsorSignature, sponsor), Ter(temMALFORMED)); + + // Invalid Sponsor Account (Sponsor.Account doesn't exist) + env(noop(alice), sponsor::As(noFunded, spfSponsorReserve), Ter(terNO_ACCOUNT)); + env(noop(alice), + sponsor::As(noFunded, spfSponsorReserve), + Sig(sfSponsorSignature, noFunded), + Ter(terNO_ACCOUNT)); + + // Invalid Flags + env(noop(alice), + sponsor::As(sponsor, (spfSponsorFee | spfSponsorReserve) + 1), + Ter(temINVALID_FLAG)); + + // SponsorFlags=0 with valid sponsor (no sponsorship purpose) + env(noop(alice), sponsor::As(sponsor, 0), Ter(temINVALID_FLAG)); + + // no SponsorFlag with valid sponsor + auto tx = noop(alice); + tx[sfSponsor.jsonName] = sponsor.human(); + env(tx, Ter(temINVALID_FLAG)); + + // Invalid Flags without sponsor + tx = noop(alice); + tx[sfSponsorFlags.jsonName] = spfSponsorFee | spfSponsorReserve; + env(tx, Ter(temINVALID_FLAG)); + } + + void + testSimpleSponsorshipSet() + { + testcase("Simple SponsorshipSet"); + using namespace test::jtx; + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, sponsor); + env.close(); + + { + // create sponsorship + env(sponsor::set( + sponsor, + tfSponsorshipSetRequireSignForFee | tfSponsorshipSetRequireSignForReserve, + 100, + XRP(100), + XRP(1)), + Fee(XRP(1)), + sponsor::SponseeAcc(alice), + Ter(tesSUCCESS)); + env.close(); + + auto sle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 100); + BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(100)); + BEAST_EXPECT(sle->at(sfMaxFee) == XRP(1)); + BEAST_EXPECT(sle->isFlag(lsfSponsorshipRequireSignForFee)); + BEAST_EXPECT(sle->isFlag(lsfSponsorshipRequireSignForReserve)); + BEAST_EXPECT(env.balance(sponsor) == XRP(10000) - sle->at(sfFeeAmount) - XRP(1)); + + // update sponsorship (decrement) + env(sponsor::set(sponsor, 0, 50, XRP(50), XRP(0.5)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tesSUCCESS)); + env.close(); + + sle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 50); + BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(50)); + BEAST_EXPECT(sle->at(sfMaxFee) == XRP(0.5)); + BEAST_EXPECT(env.balance(sponsor) == XRP(10000) - sle->at(sfFeeAmount) - XRP(2)); + + // update sponsorship (increment) + env(sponsor::set(sponsor, 0, 200, XRP(200), XRP(2)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tesSUCCESS)); + env.close(); + + sle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 200); + BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(200)); + BEAST_EXPECT(sle->at(sfMaxFee) == XRP(2)); + BEAST_EXPECT(env.balance(sponsor) == XRP(10000) - sle->at(sfFeeAmount) - XRP(3)); + + // delete from sponsor + env(sponsor::del(sponsor), sponsor::SponseeAcc(alice), Fee(XRP(1)), Ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(env.balance(sponsor) == XRP(10000) - XRP(4)); + + env(sponsor::set( + sponsor, + tfSponsorshipSetRequireSignForFee | tfSponsorshipSetRequireSignForReserve, + 100, + XRP(100), + XRP(1)), + sponsor::SponseeAcc(alice), + Ter(tesSUCCESS)); + env.close(); + + // delete from sponsee + env(sponsor::del(alice), sponsor::CounterpartySponsor(sponsor), Ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice))); + + // Cannot create sponsorship with no fee or reserve budget. MaxFee + // and flags do not make a sponsorship object useful by themselves. + env(sponsor::set(sponsor, 0), sponsor::SponseeAcc(alice), Ter(tecNO_PERMISSION)); + env.close(); + BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice))); + + env(sponsor::set_max_fee(sponsor, 0, XRP(1)), + sponsor::SponseeAcc(alice), + Ter(tecNO_PERMISSION)); + env.close(); + BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice))); + + env(sponsor::set(sponsor, 0, 0, XRP(0), XRP(0)), + sponsor::SponseeAcc(alice), + Ter(tecNO_PERMISSION)); + env.close(); + BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice))); + + // update sponsorship with non-zero value + env(sponsor::set(sponsor, 0, 100, XRP(100), XRP(1)), + sponsor::SponseeAcc(alice), + Fee(XRP(1))); + env.close(); + + sle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 100); + BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(100)); + BEAST_EXPECT(sle->at(sfMaxFee) == XRP(1)); + + // update sponsorship flags + auto testFlagUpdate = [&](auto setFlag, auto clearFlag, auto ledgerFlag) { + env(sponsor::set(sponsor, setFlag), sponsor::SponseeAcc(alice), Fee(XRP(1))); + env.close(); + + sle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->isFlag(ledgerFlag)); + + env(sponsor::set(sponsor, clearFlag), sponsor::SponseeAcc(alice), Fee(XRP(1))); + env.close(); + + sle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(!sle->isFlag(ledgerFlag)); + }; + + testFlagUpdate( + tfSponsorshipSetRequireSignForFee, + tfSponsorshipClearRequireSignForFee, + lsfSponsorshipRequireSignForFee); + testFlagUpdate( + tfSponsorshipSetRequireSignForReserve, + tfSponsorshipClearRequireSignForReserve, + lsfSponsorshipRequireSignForReserve); + + // Cannot update sponsorship so both fee and reserve budgets are absent. + env(sponsor::set(sponsor, 0, 0, XRP(0), XRP(0)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tecNO_PERMISSION)); + env.close(); + + sle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 100); + BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(100)); + BEAST_EXPECT(sle->at(sfMaxFee) == XRP(1)); + } + + { + // Removing one budget field while the other remains keeps the + // Sponsorship valid. Starting state (from above): + // RemainingOwnerCount = 100, FeeAmount = XRP(100). + + // Remove only FeeAmount (set to 0); RemainingOwnerCount remains. + env(sponsor::set_fee(sponsor, 0, XRP(0)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tesSUCCESS)); + env.close(); + + auto sle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(!sle->isFieldPresent(sfFeeAmount)); + BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 100); + + // Re-add FeeAmount, then remove only RemainingOwnerCount; + // FeeAmount remains. + env(sponsor::set_fee(sponsor, 0, XRP(100)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tesSUCCESS)); + env.close(); + + env(sponsor::set_reserve(sponsor, 0, 0), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tesSUCCESS)); + env.close(); + + sle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(!sle->isFieldPresent(sfRemainingOwnerCount)); + BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(100)); + } + + { + // Update Sponsorship (FeeAmount) + // set empty FeeAmount + env(sponsor::set_reserve(sponsor, 0, 100), sponsor::SponseeAcc(alice), Ter(tesSUCCESS)); + env.close(); + + // add FeeAmount + env(sponsor::set_fee(sponsor, 0, XRP(100)), + sponsor::SponseeAcc(alice), + Ter(tesSUCCESS)); + env.close(); + + env(sponsor::del(alice), sponsor::CounterpartySponsor(sponsor), Ter(tesSUCCESS)); + env.close(); + } + { + // Update Sponsorship (ReserveCount) + // set empty ReserveCount + env(sponsor::set_fee(sponsor, 0, XRP(100)), + sponsor::SponseeAcc(alice), + Ter(tesSUCCESS)); + env.close(); + + // add ReserveCount + env(sponsor::set_reserve(sponsor, 0, 100), sponsor::SponseeAcc(alice), Ter(tesSUCCESS)); + env.close(); + + env(sponsor::del(alice), sponsor::CounterpartySponsor(sponsor), Ter(tesSUCCESS)); + env.close(); + } + { + // delete Sponsorship (only with FeeAmount) + env(sponsor::set_fee(sponsor, 0, XRP(100)), + sponsor::SponseeAcc(alice), + Ter(tesSUCCESS)); + env.close(); + + env(sponsor::del(alice), sponsor::CounterpartySponsor(sponsor), Ter(tesSUCCESS)); + env.close(); + } + { + // delete Sponsorship (only with ReserveCount) + env(sponsor::set_reserve(sponsor, 0, 100), sponsor::SponseeAcc(alice), Ter(tesSUCCESS)); + env.close(); + + env(sponsor::del(alice), sponsor::CounterpartySponsor(sponsor), Ter(tesSUCCESS)); + env.close(); + } + } + + void + testPreFundAndCosign() + { + testcase("PreFund and Cosign"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const charlie("charlie"); + Account const sponsor("sponsor"); + + { + // Both pre-funded and co-signed; the pre-funded value is used. + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + env(sponsor::set(sponsor, 0, 100, XRP(100), XRP(1)), + sponsor::SponseeAcc(alice), + Ter(tesSUCCESS)); + env.close(); + + auto const checkSeq = env.seq(alice); + env(check::create(alice, bob, XRP(1)), + sponsor::As(sponsor, spfSponsorReserve | spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Fee(XRP(1)), + Ter(tesSUCCESS)); + env.close(); + + auto sle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 99); + BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(99)); + + env(check::cancel(alice, keylet::check(alice, checkSeq).key), Ter(tesSUCCESS)); + env.close(); + + sle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 99); // not restored + BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(99)); + } + + { + // if pre-funded value is not enough, error + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, bob, charlie, sponsor); + env.close(); + + env(sponsor::set(sponsor, 0, 1, XRP(10), XRP(100)), + sponsor::SponseeAcc(alice), + Ter(tesSUCCESS)); + env.close(); + + // Fee insufficient + env(check::create(alice, bob, XRP(1)), + sponsor::As(sponsor, spfSponsorReserve | spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Fee(XRP(11)), + Ter(terINSUF_FEE_B)); + env.close(); + + env(sponsor::set_reserve(sponsor, 0, 0), sponsor::SponseeAcc(alice), Ter(tesSUCCESS)); + env.close(); + + // reserve insufficient + env(check::create(alice, bob, XRP(1)), + sponsor::As(sponsor, spfSponsorReserve | spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Fee(XRP(1)), + Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + } + } + + void + testSponsoredFreeTierReserve() + { + testcase("Sponsored Free-Tier Reserve"); + using namespace test::jtx; + Account const alice("alice"); + Account const issuer("issuer"); + Account const sponsor("sponsor"); + + // Trust lines and MPTokens normally skip the reserve check when the + // holder's ownerCount < 2 (the "free-tier" / first-two-items shortcut). When the + // tx is sponsored, that shortcut must not apply — the sponsor must + // still cover the reserve. + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, issuer); + // Sponsor is funded just below the reserve required to cover a single + // sponsored item. + env.fund(reserve(env, 1) - drops(1), sponsor); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + + MPTTester mptt(env, issuer, {.fund = false}); + mptt.create(); + + // Free-tier trust line cosigned by an undercapitalized sponsor must + // fail — the holder's free-first-two-items shortcut does not let the + // sponsor skip the reserve check. + env(trust(alice, issuer["USD"](100)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecNO_LINE_INSUF_RESERVE)); + env.close(); + + // Free-tier MPTokenAuthorize must also fail for the same reason. + env(MPTTester::authorizeJV({.account = alice, .id = mptt.issuanceID()}), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + } + + void + testTransferSponsor() + { + testcase("Transfer Sponsor"); + using namespace test::jtx; + + // Verify preflight checks + { + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + // Test invalid flags for SponsorshipTransfer + { + // Invalid flag is provided + env(sponsor::transfer( + alice, + (tfSponsorshipCreate | tfSponsorshipReassign | tfSponsorshipEnd) + 1), + Ter(temINVALID_FLAG)); + + // No SponsorshipTransfer flag is provided + env(sponsor::transfer(alice, 0), Ter(temINVALID_FLAG)); + + // Only one of the three valid flags can be set. + // Setting more than one flag is invalid + for (auto flag : { + tfSponsorshipCreate | tfSponsorshipReassign, + tfSponsorshipCreate | tfSponsorshipEnd, + tfSponsorshipReassign | tfSponsorshipEnd, + tfSponsorshipCreate | tfSponsorshipReassign | tfSponsorshipEnd, + }) + env(sponsor::transfer(alice, flag), Ter(temINVALID_FLAG)); + } + + // Malformed tests for tfSponsorshipCreate + { + // No sponsor field present + env(sponsor::transfer(alice, tfSponsorshipCreate), Ter(temMALFORMED)); + + // Sponsor field present without spfSponsorReserve + env(sponsor::transfer(alice, tfSponsorshipCreate), + sponsor::As(sponsor, spfSponsorFee), + Ter(temINVALID_FLAG)); + + // Sponsee field present + env(sponsor::transfer(alice, tfSponsorshipCreate), + sponsor::SponseeAcc(bob), + sponsor::As(sponsor, spfSponsorReserve), + Ter(temMALFORMED)); + } + + // Malformed tests for tfSponsorshipReassign + { + // No sponsor field present + env(sponsor::transfer(alice, tfSponsorshipReassign), Ter(temMALFORMED)); + + // Sponsor field present without spfSponsorReserve + env(sponsor::transfer(alice, tfSponsorshipReassign), + sponsor::As(sponsor, spfSponsorFee), + Ter(temINVALID_FLAG)); + + // Sponsee field present + env(sponsor::transfer(alice, tfSponsorshipReassign), + sponsor::SponseeAcc(bob), + sponsor::As(sponsor, spfSponsorReserve), + Ter(temMALFORMED)); + } + + // Malformed tests for tfSponsorshipEnd + { + // Sponsor field present + env(sponsor::transfer(alice, tfSponsorshipEnd), + sponsor::As(sponsor, spfSponsorReserve), + Ter(temMALFORMED)); + + // SponsorFlags field present + auto tx = sponsor::transfer(alice, tfSponsorshipEnd); + tx[sfSponsorFlags.jsonName] = spfSponsorFee; + env(tx, Ter(temINVALID_FLAG)); + + // Account = Sponsee + env(sponsor::transfer(alice, tfSponsorshipEnd), + sponsor::SponseeAcc(alice), + Ter(temMALFORMED)); + } + } + + { + // Invalid SponsorshipEnd permission (sponsor object/sponsor account) + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const charlie("charlie"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + { + // sponsor object + env.fund(XRP(1000), charlie); + env.close(); + env(deposit::auth(alice, charlie), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + auto const keylet = keylet::depositPreauth(alice, charlie); + env(sponsor::transfer(bob, tfSponsorshipEnd, keylet.key), + sponsor::SponseeAcc(alice), + Ter(tecNO_PERMISSION)); + } + { + // sponsor object + env(sponsor::transfer(alice, tfSponsorshipCreate), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + env(sponsor::transfer(bob, tfSponsorshipEnd), + sponsor::SponseeAcc(alice), + Ter(tecNO_PERMISSION)); + } + { + // The provided sfSponsee account does not exist + // when ending sponsorship. + Account const ghost("ghost"); // never funded, absent from ledger + env(sponsor::transfer(sponsor, tfSponsorshipEnd), + sponsor::SponseeAcc(ghost), + Ter(terNO_ACCOUNT)); + } + } + + { + // sponsor account + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor1("sponsor1"); + Account const sponsor2("sponsor2"); + env.fund(XRP(10000), alice, bob, sponsor1, sponsor2); + + // sfSponsor provided but sfSponsorSignature not provided + env(sponsor::transfer(alice, tfSponsorshipCreate), + sponsor::As(sponsor1, spfSponsorReserve), + Ter(temMALFORMED)); + env.close(); + + adjustAccountXRPBalance(env, sponsor1, accountReserve(env, 2) - drops(1)); + + env(sponsor::transfer(alice, tfSponsorshipCreate), + sponsor::As(sponsor1, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor1), + Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + adjustAccountXRPBalance(env, sponsor1, accountReserve(env, 2)); + + env(sponsor::transfer(alice, tfSponsorshipCreate), + sponsor::As(sponsor1, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor1)); + env.close(); + + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 1); + auto const sle1 = env.le(keylet::account(alice)); + BEAST_EXPECT(sle1->isFieldPresent(sfSponsor)); + BEAST_EXPECT(sle1->getAccountID(sfSponsor) == sponsor1.id()); + + // transfer sponsor + adjustAccountXRPBalance(env, sponsor2, accountReserve(env, 2) - drops(1)); + + env(sponsor::transfer(alice, tfSponsorshipReassign), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2), + Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + adjustAccountXRPBalance(env, sponsor2, accountReserve(env, 2)); + + env(sponsor::transfer(alice, tfSponsorshipReassign), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 1); + BEAST_EXPECT( + !env.le(keylet::account(sponsor1))->isFieldPresent(sfSponsoringAccountCount)); + auto const sle2 = env.le(keylet::account(alice)); + BEAST_EXPECT(sle2->isFieldPresent(sfSponsor)); + BEAST_EXPECT(sle2->getAccountID(sfSponsor) == sponsor2.id()); + + // Reassign to the current sponsor is a no-op and is rejected + env(sponsor::transfer(alice, tfSponsorshipReassign), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2), + Ter(tecNO_PERMISSION)); + env.close(); + + BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 1); + + // sponsor 2 accounts + adjustAccountXRPBalance(env, sponsor2, accountReserve(env, 3)); + env(sponsor::transfer(bob, tfSponsorshipCreate), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + + // dissolve sponsors + adjustAccountXRPBalance(env, alice, accountReserve(env, 1) - drops(1)); + + env(sponsor::transfer(alice, tfSponsorshipEnd), Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + adjustAccountXRPBalance(env, alice, accountReserve(env, 1)); + + env(sponsor::transfer(alice, tfSponsorshipEnd)); + env.close(); + + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 1); + auto const sle3 = env.le(keylet::account(alice)); + BEAST_EXPECT(!sle3->isFieldPresent(sfSponsor)); + + env(sponsor::transfer(bob, tfSponsorshipEnd)); + env.close(); + + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, bob) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, bob) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0); + BEAST_EXPECT( + !env.le(keylet::account(sponsor2))->isFieldPresent(sfSponsoringAccountCount)); + auto const sle4 = env.le(keylet::account(bob)); + BEAST_EXPECT(!sle4->isFieldPresent(sfSponsor)); + + // not sponsored + env(sponsor::transfer(bob, tfSponsorshipEnd), Ter(tecNO_PERMISSION)); + env.close(); + } + { + // dissolve account sponsorship from sponsor + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + env(sponsor::transfer(alice, tfSponsorshipCreate), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + BEAST_EXPECT(env.le(alice)->getAccountID(sfSponsor) == sponsor.id()); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor) == 1); + + env(sponsor::transfer(sponsor, tfSponsorshipEnd), sponsor::SponseeAcc(alice)); + env.close(); + + BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfSponsor)); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor) == 0); + } + + { + // sponsor object (co-signing) + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor1("sponsor1"); + Account const sponsor2("sponsor2"); + env.fund(XRP(10000), alice, bob, sponsor1, sponsor2); + env.close(); + + adjustAccountXRPBalance(env, sponsor1, reserve(env, 1) - drops(1)); + adjustAccountXRPBalance(env, sponsor2, reserve(env, 1) - drops(1)); + + auto const seq = env.seq(alice); + env(check::create(alice, bob, XRP(1))); + env.close(); + + auto const checkId = keylet::check(alice, seq).key; + BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr); + + env(sponsor::transfer(alice, tfSponsorshipCreate, checkId), + sponsor::As(sponsor1, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor1), + Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + env(pay(alice, sponsor1, drops(1))); + env.close(); + + // Invalid ObjectID (not found) + env(sponsor::transfer(alice, tfSponsorshipCreate, keylet::check(alice, 0).key), + sponsor::As(sponsor1, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor1), + Ter(tecNO_ENTRY)); + env.close(); + + // Invalid Owner + env(sponsor::transfer(bob, tfSponsorshipCreate, checkId), + sponsor::As(sponsor1, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor1), + Ter(tecNO_PERMISSION)); + env.close(); + + // Reassign an object that is not sponsored yet + env(sponsor::transfer(alice, tfSponsorshipReassign, checkId), + sponsor::As(sponsor1, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor1), + Ter(tecNO_PERMISSION)); + env.close(); + + // Valid Owner + env(sponsor::transfer(alice, tfSponsorshipCreate, checkId), + sponsor::As(sponsor1, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor1)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 1); + BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0); + auto const sle1 = env.le(keylet::unchecked(checkId)); + BEAST_EXPECT(sle1->isFieldPresent(sfSponsor)); + BEAST_EXPECT(sle1->getAccountID(sfSponsor) == sponsor1.id()); + + // Create on an object that is already sponsored + env(sponsor::transfer(alice, tfSponsorshipCreate, checkId), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2), + Ter(tecNO_PERMISSION)); + env.close(); + + // Reassign to the current sponsor is a no-op and is rejected + env(sponsor::transfer(alice, tfSponsorshipReassign, checkId), + sponsor::As(sponsor1, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor1), + Ter(tecNO_PERMISSION)); + env.close(); + + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 1); + + // transfer sponsor + env(sponsor::transfer(alice, tfSponsorshipReassign, checkId), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2), + Ter(tecINSUFFICIENT_RESERVE)); + + env(pay(alice, sponsor2, drops(1))); + env.close(); + + env(sponsor::transfer(alice, tfSponsorshipReassign, checkId), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0); + auto const sle2 = env.le(keylet::unchecked(checkId)); + BEAST_EXPECT(sle2->isFieldPresent(sfSponsor)); + BEAST_EXPECT(sle2->getAccountID(sfSponsor) == sponsor2.id()); + + // dissolve sponsor: ending an object sponsorship succeeds even + // when the sponsee lacks sufficient reserve to reclaim the object. + adjustAccountXRPBalance(env, alice, reserve(env, 1) - drops(1)); + + env(sponsor::transfer(alice, tfSponsorshipEnd, checkId)); + env.close(); + + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0); + BEAST_EXPECT( + !env.le(keylet::account(sponsor2))->isFieldPresent(sfSponsoringOwnerCount)); + auto const sle3 = env.le(keylet::unchecked(checkId)); + BEAST_EXPECT(!sle3->isFieldPresent(sfSponsor)); + + // Ending sponsorship on an object that is not sponsored (a ticket, + // which cannot be sponsored) is rejected. + adjustAccountXRPBalance(env, alice, reserve(env, 2)); + auto const ticketSeq = env.seq(alice); + env(ticket::create(alice, 1)); + env.close(); + auto ticketId = keylet::ticket(alice, ticketSeq + 1).key; + BEAST_EXPECT(env.le(keylet::unchecked(ticketId))); + env(sponsor::transfer(alice, tfSponsorshipEnd, ticketId), Ter(tecNO_PERMISSION)); + env.close(); + env(noop(alice), ticket::Use(ticketSeq + 1)); + env.close(); + } + { + // sponsor object (pre-funded + no ltSponsorship entry) + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor1("sponsor1"); + Account const sponsor2("sponsor2"); + env.fund(XRP(10000), alice, bob, sponsor1, sponsor2); + env.close(); + + auto const seq = env.seq(alice); + env(check::create(alice, bob, XRP(1))); + env.close(); + + auto const checkId = keylet::check(alice, seq).key; + BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr); + + env(sponsor::transfer(alice, tfSponsorshipCreate, checkId), + sponsor::As(sponsor1, spfSponsorReserve), + Ter(terNO_PERMISSION)); + env.close(); + + env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice)); + env.close(); + + env(sponsor::transfer(alice, tfSponsorshipCreate, checkId), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + + env(sponsor::transfer(alice, tfSponsorshipReassign, checkId), + sponsor::As(sponsor1, spfSponsorReserve), + Ter(terNO_PERMISSION)); + env.close(); + } + { + // sponsor object (pre-funded) + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor1("sponsor1"); + Account const sponsor2("sponsor2"); + env.fund(XRP(10000), alice, bob, sponsor1, sponsor2); + env.close(); + + auto const seq = env.seq(alice); + env(check::create(alice, bob, XRP(1))); + env.close(); + + auto const checkId = keylet::check(alice, seq).key; + BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr); + + // insufficient reserve count + env(sponsor::set_fee(sponsor1, 0, XRP(100)), sponsor::SponseeAcc(alice)); + env.close(); + env(sponsor::transfer(alice, tfSponsorshipCreate, checkId), + sponsor::As(sponsor1, spfSponsorReserve), + Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + env(sponsor::set_reserve(sponsor1, 0, 100), sponsor::SponseeAcc(alice)); + env.close(); + + env(sponsor::transfer(alice, tfSponsorshipCreate, checkId), + sponsor::As(sponsor1, spfSponsorReserve)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 1); + BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0); + auto checkSle = env.le(keylet::unchecked(checkId)); + BEAST_EXPECT(checkSle->isFieldPresent(sfSponsor)); + BEAST_EXPECT(checkSle->getAccountID(sfSponsor) == sponsor1.id()); + auto sponsor1Sle = env.le(keylet::sponsorship(sponsor1, alice)); + BEAST_EXPECT(sponsor1Sle->getFieldU32(sfRemainingOwnerCount) == 99); + + // Reassign to the current sponsor is rejected and must not draw + // down the pre-funded reserve budget + env(sponsor::transfer(alice, tfSponsorshipReassign, checkId), + sponsor::As(sponsor1, spfSponsorReserve), + Ter(tecNO_PERMISSION)); + env.close(); + + sponsor1Sle = env.le(keylet::sponsorship(sponsor1, alice)); + BEAST_EXPECT(sponsor1Sle->getFieldU32(sfRemainingOwnerCount) == 99); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 1); + + // transfer sponsor + env(sponsor::set_reserve(sponsor2, 0, 100), sponsor::SponseeAcc(alice)); + env.close(); + + env(sponsor::transfer(alice, tfSponsorshipReassign, checkId), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0); + checkSle = env.le(keylet::unchecked(checkId)); + BEAST_EXPECT(checkSle->isFieldPresent(sfSponsor)); + BEAST_EXPECT(checkSle->getAccountID(sfSponsor) == sponsor2.id()); + sponsor1Sle = env.le(keylet::sponsorship(sponsor1, alice)); + BEAST_EXPECT(sponsor1Sle->getFieldU32(sfRemainingOwnerCount) == 99); + auto sponsor2Sle = env.le(keylet::sponsorship(sponsor2, alice)); + BEAST_EXPECT(sponsor2Sle->getFieldU32(sfRemainingOwnerCount) == 99); + + // dissolve sponsor + adjustAccountXRPBalance(env, alice, reserve(env, 1)); + env(sponsor::transfer(alice, tfSponsorshipEnd, checkId)); + env.close(); + + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0); + BEAST_EXPECT( + !env.le(keylet::account(sponsor2))->isFieldPresent(sfSponsoringOwnerCount)); + checkSle = env.le(keylet::unchecked(checkId)); + BEAST_EXPECT(!checkSle->isFieldPresent(sfSponsor)); + sponsor2Sle = env.le(keylet::sponsorship(sponsor2, alice)); + BEAST_EXPECT(sponsor2Sle->getFieldU32(sfRemainingOwnerCount) == 99); + } + + { + // Dissolve object sponsorship from sponsor(no-ltSponsorship) + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + auto const seq = env.seq(alice); + env(check::create(alice, bob, XRP(1))); + env.close(); + + auto const checkId = keylet::check(alice, seq).key; + BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr); + + env(sponsor::transfer(alice, tfSponsorshipCreate, checkId), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + BEAST_EXPECT( + env.le(keylet::unchecked(checkId))->getAccountID(sfSponsor) == sponsor.id()); + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + // not the owner of the object + env(sponsor::transfer(sponsor, tfSponsorshipEnd, checkId), Ter(tecNO_PERMISSION)); + env.close(); + + env(sponsor::transfer(sponsor, tfSponsorshipEnd, checkId), sponsor::SponseeAcc(alice)); + env.close(); + + BEAST_EXPECT(!env.le(keylet::unchecked(checkId))->isFieldPresent(sfSponsor)); + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + } + + { + // Dissolve object sponsorship from sponsor (with ltSponsorship) + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + auto const seq = env.seq(alice); + env(check::create(alice, bob, XRP(1))); + env.close(); + + auto const checkId = keylet::check(alice, seq).key; + BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr); + + env(sponsor::transfer(alice, tfSponsorshipCreate, checkId), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + env(sponsor::set_reserve(sponsor, 0, 100), sponsor::SponseeAcc(alice)); + env.close(); + + BEAST_EXPECT( + env.le(keylet::unchecked(checkId))->getAccountID(sfSponsor) == sponsor.id()); + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + BEAST_EXPECT( + env.le(keylet::sponsorship(sponsor, alice))->getFieldU32(sfRemainingOwnerCount) == + 100); + + // not the owner of the object + env(sponsor::transfer(sponsor, tfSponsorshipEnd, checkId), Ter(tecNO_PERMISSION)); + env.close(); + + env(sponsor::transfer(sponsor, tfSponsorshipEnd, checkId), sponsor::SponseeAcc(alice)); + env.close(); + + BEAST_EXPECT(!env.le(keylet::unchecked(checkId))->isFieldPresent(sfSponsor)); + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT( + env.le(keylet::sponsorship(sponsor, alice))->getFieldU32(sfRemainingOwnerCount) == + 100); + } + + { + // sponsor trustline + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + + auto const& highAcc = alice > bob ? alice : bob; + auto const& lowAcc = alice > bob ? bob : alice; + + for (bool const isIssuerHigh : {false, true}) + { + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + auto const& issuer = isIssuerHigh ? highAcc : lowAcc; + auto const& user = isIssuerHigh ? lowAcc : highAcc; + + auto const usd = issuer["usd"]; + auto const currency = usd.currency; + + env(trust(user, issuer["usd"](100))); + env.close(); + + auto const trustId = keylet::trustLine(user, issuer, currency); + BEAST_EXPECT(env.le(trustId)); + + // transfer sponsor + env(sponsor::transfer(user, tfSponsorshipCreate, trustId.key), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + BEAST_EXPECT(env.le(trustId)); + + BEAST_EXPECT( + env.le(trustId)->getAccountID(isIssuerHigh ? sfLowSponsor : sfHighSponsor) == + sponsor.id()); + BEAST_EXPECT( + !env.le(trustId)->isFieldPresent(isIssuerHigh ? sfHighSponsor : sfLowSponsor)); + + // dissolve sponsor + env(sponsor::transfer(user, tfSponsorshipEnd, trustId.key)); + env.close(); + + BEAST_EXPECT(env.le(trustId)); + BEAST_EXPECT( + !env.le(trustId)->isFieldPresent(isIssuerHigh ? sfLowSponsor : sfHighSponsor)); + BEAST_EXPECT( + !env.le(trustId)->isFieldPresent(isIssuerHigh ? sfHighSponsor : sfLowSponsor)); + } + } + + { + // invalid transfer + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + // create owner dir + env(ticket::create(alice, 1)); + env.close(); + + // AccountRoot + // Amendments + // LedgerHashes + // FeeSettings + // NegativeUNL + // DirNode + auto const keylets = { + keylet::account(alice), + // keylet::amendments(), + keylet::skip(), + keylet::feeSettings(), + // keylet::negativeUNL(), + keylet::ownerDir(alice), + }; + for (auto const& keylet : keylets) + { + env(sponsor::transfer(alice, tfSponsorshipCreate, keylet.key), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecNO_PERMISSION)); + } + } + + { + // existing owner objects that are outside the v1 SponsorshipTransfer + // object allow-list + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, sponsor); + env.close(); + + auto const checkBlocked = [&](Account const& account, uint256 const& objectID) { + env(sponsor::transfer(account, tfSponsorshipCreate, objectID), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecNO_PERMISSION)); + env.close(); + }; + + auto const ticketSeq = env.seq(alice); + env(ticket::create(alice, 1)); + env.close(); + auto const ticketID = keylet::ticket(alice, ticketSeq + 1).key; + BEAST_EXPECT(env.le(keylet::unchecked(ticketID))); + checkBlocked(alice, ticketID); + + env(did::setValid(alice)); + env.close(); + auto const didKeylet = keylet::did(alice.id()); + BEAST_EXPECT(env.le(didKeylet)); + checkBlocked(alice, didKeylet.key); + + env(token::mint(alice, 0u)); + env.close(); + auto const nftPageKeylet = keylet::nftokenPageMax(alice); + BEAST_EXPECT(env.le(nftPageKeylet)); + checkBlocked(alice, nftPageKeylet.key); + + Account const borrower("borrower"); + env.fund(XRP(1000000), borrower); + env.close(); + + PrettyAsset const xrpAsset{xrpIssue(), 1'000'000}; + Vault const vault{env}; + auto [vaultTx, vaultKeylet] = vault.create({.owner = alice, .asset = xrpAsset}); + env(vaultTx); + env.close(); + + env(vault.deposit( + {.depositor = alice, .id = vaultKeylet.key, .amount = xrpAsset(1000)})); + env.close(); + + auto const brokerKeylet = keylet::loanBroker(alice.id(), env.seq(alice)); + env(loanBroker::set(alice, vaultKeylet.key), + loanBroker::kDebtMaximum(xrpAsset(1000).value()), + loanBroker::kManagementFeeRate(TenthBips16{0}), + loanBroker::kCoverRateMinimum(TenthBips32{0}), + loanBroker::kCoverRateLiquidation(TenthBips32{0})); + env.close(); + + auto const loanKeylet = keylet::loan(brokerKeylet.key, 1); + env(loan::set(borrower, brokerKeylet.key, xrpAsset(100).value()), + Sig(sfCounterpartySignature, alice), + Fee(env.current()->fees().base * 2)); + env.close(); + BEAST_EXPECT(env.le(loanKeylet)); + checkBlocked(borrower, loanKeylet.key); + } + } + + void + testSponsorFee() + { + using namespace test::jtx; + + testcase("Sponsor Fee"); + + { + // co-signing + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob); + env.close(); + + { + // Fee should be checked before sponsor permission, otherwise a tec + // result from a later check could cause context reset to pay Fee. + auto aliceBalance = env.balance(alice); + auto bobBalance = env.balance(bob); + auto sponsorBalance = env.balance(sponsor); + + env(pay(alice, bob, XRP(100)), + Fee(XRP(2000)), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(terNO_ACCOUNT)); + env.close(); + BEAST_EXPECT(env.balance(alice) == aliceBalance); + BEAST_EXPECT(env.balance(bob) == bobBalance); + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance); + } + + env.fund(XRP(1000), sponsor); + env.close(); + + { + // Sponsor pays the Fee + auto aliceBalance = env.balance(alice); + auto bobBalance = env.balance(bob); + auto sponsorBalance = env.balance(sponsor); + + auto const sendAmt = XRP(100); + auto const feeAmt = XRP(10); + env(pay(alice, bob, sendAmt), + Fee(feeAmt), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor)); + env.close(); + BEAST_EXPECT(env.balance(alice) == aliceBalance - sendAmt); + BEAST_EXPECT(env.balance(bob) == bobBalance + sendAmt); + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance - feeAmt); + } + + { + // insufficient balance to pay Fee + auto aliceBalance = env.balance(alice); + auto bobBalance = env.balance(bob); + auto sponsorBalance = env.balance(sponsor); + + env(pay(alice, bob, XRP(100)), + Fee(XRP(2000)), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(terINSUF_FEE_B)); + env.close(); + BEAST_EXPECT(env.balance(alice) == aliceBalance); + BEAST_EXPECT(env.balance(bob) == bobBalance); + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance); + } + + { + // Fee is paid by Sponsor + // on context reset (tec error) + auto aliceBalance = env.balance(alice); + auto bobBalance = env.balance(bob); + auto sponsorBalance = env.balance(sponsor); + auto const feeAmt = XRP(10); + + env(pay(alice, bob, XRP(20000)), + Fee(feeAmt), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(tecUNFUNDED_PAYMENT)); + env.close(); + + BEAST_EXPECT(env.balance(alice) == aliceBalance); + BEAST_EXPECT(env.balance(bob) == bobBalance); + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance - feeAmt); + } + + { + // A co-signed sponsor pays the fee from its own balance, but + // must never be charged into its own account reserve. With a + // balance of exactly reserve + fee the fee is still payable, + // charging the sponsor down to precisely its reserve. + auto const feeAmt = XRP(10); + adjustAccountXRPBalance(env, sponsor, reserve(env, 0) + feeAmt); + auto const sponsorBalance = env.balance(sponsor); + + env(noop(alice), + Fee(feeAmt), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance - feeAmt); + BEAST_EXPECT(env.balance(sponsor) == reserve(env, 0)); + } + + { + // below reserve + adjustAccountXRPBalance(env, sponsor, env.current()->fees().reserve); + + env(noop(alice), + Fee(env.current()->fees().base), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(terINSUF_FEE_B)); + env.close(); + + env(noop(alice), + Fee(XRP(10)), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(terINSUF_FEE_B)); + env.close(); + } + } + + { + // pre funded + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + { + // Fee should be checked before sponsor permission, otherwise a tec + // result from a later check could cause context reset to pay Fee. + auto aliceBalance = env.balance(alice); + auto bobBalance = env.balance(bob); + auto sponsorBalance = env.balance(sponsor); + + env(pay(alice, bob, XRP(100)), + Fee(XRP(2000)), + sponsor::As(sponsor, spfSponsorFee), + Ter(terNO_PERMISSION)); + env.close(); + BEAST_EXPECT(env.balance(alice) == aliceBalance); + BEAST_EXPECT(env.balance(bob) == bobBalance); + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance); + } + + env(sponsor::set_fee(sponsor, 0, XRP(100)), sponsor::SponseeAcc(alice)); + env.close(); + + { + // Sponsor pays the Fee + auto aliceBalance = env.balance(alice); + auto bobBalance = env.balance(bob); + auto sponsorBalance = env.balance(sponsor); + auto sponsorFee = sponsor::sponsorshipFeeBalance(env, sponsor, alice); + + auto const sendAmt = XRP(100); + auto const feeAmt = XRP(10); + env(pay(alice, bob, sendAmt), Fee(feeAmt), sponsor::As(sponsor, spfSponsorFee)); + env.close(); + + BEAST_EXPECT(env.balance(alice) == aliceBalance - sendAmt); + BEAST_EXPECT(env.balance(bob) == bobBalance + sendAmt); + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance); + BEAST_EXPECT( + sponsor::sponsorshipFeeBalance(env, sponsor, alice) == sponsorFee - feeAmt); + } + + { + // insufficient balance to pay Fee + { + // > FeeAmount + auto aliceBalance = env.balance(alice); + auto bobBalance = env.balance(bob); + auto sponsorBalance = env.balance(sponsor); + auto sponsorFee = sponsor::sponsorshipFeeBalance(env, sponsor, alice); + + env(pay(alice, bob, XRP(100)), + Fee(XRP(90) + drops(1)), + sponsor::As(sponsor, spfSponsorFee), + Ter(terINSUF_FEE_B)); + env.close(); + + BEAST_EXPECT(env.balance(alice) == aliceBalance); + BEAST_EXPECT(env.balance(bob) == bobBalance); + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance); + BEAST_EXPECT(sponsor::sponsorshipFeeBalance(env, sponsor, alice) == sponsorFee); + } + // use all FeeAmount + { + // = FeeAmount + auto aliceBalance = env.balance(alice); + auto bobBalance = env.balance(bob); + auto sponsorBalance = env.balance(sponsor); + + env(pay(alice, bob, XRP(100)), + Fee(XRP(90)), + sponsor::As(sponsor, spfSponsorFee), + Ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(env.balance(alice) == aliceBalance - XRP(100)); + BEAST_EXPECT(env.balance(bob) == bobBalance + XRP(100)); + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance); + BEAST_EXPECT( + !env.le(keylet::sponsorship(sponsor, alice))->isFieldPresent(sfFeeAmount)); + } + + // reset FeeAmount and MaxFee + env(sponsor::del(sponsor), sponsor::SponseeAcc(alice)); + env.close(); + env(sponsor::set_fee(sponsor, 0, XRP(10), XRP(1)), sponsor::SponseeAcc(alice)); + env.close(); + + { + // > MaxFee + auto aliceBalance = env.balance(alice); + auto bobBalance = env.balance(bob); + auto sponsorBalance = env.balance(sponsor); + auto sponsorFee = sponsor::sponsorshipFeeBalance(env, sponsor, alice); + + env(pay(alice, bob, XRP(100)), + Fee(XRP(1) + drops(1)), + sponsor::As(sponsor, spfSponsorFee), + Ter(terINSUF_FEE_B)); + env.close(); + + BEAST_EXPECT(env.balance(alice) == aliceBalance); + BEAST_EXPECT(env.balance(bob) == bobBalance); + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance); + BEAST_EXPECT(sponsor::sponsorshipFeeBalance(env, sponsor, alice) == sponsorFee); + } + } + + { + // Fee is paid by Sponsor + // on context reset (tec error) + auto aliceBalance = env.balance(alice); + auto bobBalance = env.balance(bob); + auto sponsorBalance = env.balance(sponsor); + auto sponsorFee = sponsor::sponsorshipFeeBalance(env, sponsor, alice); + auto const feeAmt = XRP(1); + + env(pay(alice, bob, XRP(20000)), + Fee(feeAmt), + sponsor::As(sponsor, spfSponsorFee), + Ter(tecUNFUNDED_PAYMENT)); + env.close(); + + BEAST_EXPECT(env.balance(alice) == aliceBalance); + BEAST_EXPECT(env.balance(bob) == bobBalance); + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance); + BEAST_EXPECT( + sponsor::sponsorshipFeeBalance(env, sponsor, alice) == sponsorFee - feeAmt); + } + + // make sfFeeAmount absent if tec error and all Fee is paid + { + // reset FeeAmount and MaxFee + env(sponsor::del(sponsor), sponsor::SponseeAcc(alice)); + env(sponsor::set_fee(sponsor, 0, XRP(10)), sponsor::SponseeAcc(alice)); + env.close(); + + BEAST_EXPECT( + env.le(keylet::sponsorship(sponsor, alice))->isFieldPresent(sfFeeAmount)); + auto sponsorAvailableFee = sponsor::sponsorshipFeeBalance(env, sponsor, alice); + env(check::cancel(alice, uint256(1)), + Fee(sponsorAvailableFee), + sponsor::As(sponsor, spfSponsorFee), + Ter(tecNO_ENTRY)); + env.close(); + BEAST_EXPECT( + !env.le(keylet::sponsorship(sponsor, alice))->isFieldPresent(sfFeeAmount)); + } + } + + // MaxFee cap is enforced in reset() for tec-failing transactions. + // On a closed ledger view (!view.open()), checkFee returns tecINSUFF_FEE when + // Fee > MaxFee (not terINSUF_FEE_B), triggering reset() + { + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const carol("sponsor"); + + env.fund(XRP(10000), alice, carol); + env.close(); + + // FeeAmount=1000 drops, MaxFee=10 drops + env(sponsor::set_fee(carol, 0, drops(1000), drops(10)), sponsor::SponseeAcc(alice)); + env.close(); + + // Apply directly against the closed ledger view (open_ = false) so that + // checkFee returns tecINSUFF_FEE and reset() is invoked. + OpenView overlay(&*env.closed()); + + auto jt = env.jt( + noop(alice), + Fee(drops(1000)), + Seq(env.seq(alice)), + sponsor::As(carol, spfSponsorFee)); + + auto const result = xrpl::apply(env.app(), overlay, *jt.stx, TapNone, env.journal); + BEAST_EXPECT(result.ter == tecINSUFF_FEE); + BEAST_EXPECT(result.applied); + + // Only MaxFee (10 drops) must be deducted, not the full 1000 drops. + auto const sle = overlay.read(keylet::sponsorship(carol.id(), alice.id())); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->isFieldPresent(sfFeeAmount)); + BEAST_EXPECT(sle->getFieldAmount(sfFeeAmount) == drops(990)); // 1000 - MaxFee(10) + } + + // LedgerStateFix charges an owner-reserve fee and can claim that fee + // while returning tecFAILED_PROCESSING. That path must be safe when the + // fee is pre-funded by a sponsorship object. + { + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const sponsor("sponsor"); + env.fund(XRP(1000), alice, sponsor); + env.close(); + + auto const fixFee = drops(env.current()->fees().increment); + env(sponsor::set_fee(sponsor, 0, fixFee), sponsor::SponseeAcc(alice)); + env.close(); + + env(ledgerStateFix::nftPageLinks(alice, alice), + Fee(fixFee), + sponsor::As(sponsor, spfSponsorFee), + Ter(tecFAILED_PROCESSING)); + + if (auto const sle = env.le(keylet::sponsorship(sponsor, alice)); BEAST_EXPECT(sle)) + BEAST_EXPECT(!sle->isFieldPresent(sfFeeAmount)); + } + + // If preclaim saw spendable sponsored FeeAmount but the apply view no + // longer has it, the fee path should fail cleanly instead of throwing. + { + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const sponsor("sponsor"); + env.fund(XRP(1000), alice, sponsor); + env.close(); + + auto const fixFee = drops(env.current()->fees().increment); + env(sponsor::set_fee(sponsor, 0, fixFee), sponsor::SponseeAcc(alice)); + env.close(); + + OpenView overlay(&*env.closed()); + auto jt = env.jt( + ledgerStateFix::nftPageLinks(alice, alice), + Fee(fixFee), + sponsor::As(sponsor, spfSponsorFee)); + + auto const pf = preflight(env.app(), overlay.rules(), *jt.stx, TapNone, env.journal); + BEAST_EXPECT(isTesSuccess(pf.ter)); + auto const pc = preclaim(pf, env.app(), overlay); + BEAST_EXPECT(isTesSuccess(pc.ter)); + + auto const original = overlay.read(keylet::sponsorship(sponsor, alice)); + if (BEAST_EXPECT(original)) + { + auto sle = std::make_shared(*original); + sle->makeFieldAbsent(sfFeeAmount); + overlay.rawReplace(sle); + } + + auto const result = doApply(pc, env.app(), overlay); + BEAST_EXPECT(result.ter == terINSUF_FEE_B); + BEAST_EXPECT(!result.applied); + } + + // test lsfSponsorshipRequireSignForFee + { + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + // set flag + env(sponsor::set_fee(sponsor, tfSponsorshipSetRequireSignForFee, XRP(10)), + sponsor::SponseeAcc(alice)); + env.close(); + + env(pay(alice, bob, XRP(100)), + Fee(XRP(10)), + sponsor::As(sponsor, spfSponsorFee), + Ter(terNO_PERMISSION)); + env.close(); + + BEAST_EXPECT( + env.le(keylet::sponsorship(sponsor, alice))->getFieldAmount(sfFeeAmount) == + XRP(10)); + + // clear flag + env(sponsor::set_fee(sponsor, tfSponsorshipClearRequireSignForFee, XRP(10)), + sponsor::SponseeAcc(alice)); + env.close(); + + // Payment is re-applied + BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice))->isFieldPresent(sfFeeAmount)); + } + + // RequireSignForFee: co-signing should succeed + { + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + // set flag + env(sponsor::set_fee(sponsor, tfSponsorshipSetRequireSignForFee, XRP(10)), + sponsor::SponseeAcc(alice)); + env.close(); + + // pre-funded (no sig) should fail + env(pay(alice, bob, XRP(100)), + Fee(XRP(1)), + sponsor::As(sponsor, spfSponsorFee), + Ter(terNO_PERMISSION)); + env.close(); + + // co-signing (with sig) should succeed + env(pay(alice, bob, XRP(100)), + Fee(XRP(1)), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT( + env.le(keylet::sponsorship(sponsor, alice))->getFieldAmount(sfFeeAmount) == XRP(9)); + } + } + + void + testSponsorAccount() + { + testcase("Sponsor Account"); + using namespace test::jtx; + + Account const alice("alice"); + Account const sponsor("sponsor"); + Account const sponsor2("sponsor2"); + Account const sponsor3("sponsor3"); + Account const bob("bob"); + Account const charlie("charlie"); + Account const dave("dave"); + Account const gw("gw"); + auto const usd = gw["usd"]; + + { + // Disabled + Env env{*this, testableAmendments() - featureSponsor}; + env.fund(XRP(10000), alice, sponsor); + env.close(); + env(pay(alice, bob, XRP(100)), Txflags(tfSponsorCreatedAccount), Ter(temDISABLED)); + env.close(); + } + + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, sponsor, sponsor2, sponsor3); + env.close(); + + // Invalid flags + for (auto flag : { + tfNoRippleDirect, + tfPartialPayment, + tfLimitQuality, + }) + { + env(pay(alice, bob, XRP(100)), + Txflags(tfSponsorCreatedAccount | flag), + Ter(temINVALID_FLAG)); + env.close(); + } + + // Invalid amount(iou) + env(pay(alice, bob, usd(100)), Txflags(tfSponsorCreatedAccount), Ter(temBAD_AMOUNT)); + env.close(); + + // Sponsored account creation is reserve sponsorship and is only supported for direct XRP + // payments. + env(pay(alice, bob, drops(1)), + Txflags(tfSponsorCreatedAccount), + Sendmax(usd(2)), + Ter(temINVALID)); + env.close(); + + env(pay(alice, bob, drops(1)), + Txflags(tfSponsorCreatedAccount), + Path(~XRP), + Ter(temINVALID)); + env.close(); + + // Account is not sponsored by normal Sponsor specification + { + env(pay(alice, bob, drops(env.current()->fees().accountReserve(0, 1))), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + auto const bobSle = env.le(keylet::account(bob)); + BEAST_EXPECT(!bobSle->isFieldPresent(sfSponsor)); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor) == 0); + } + + // Use tfSponsorCreatedAccount to sponsor an account + { + // to funded account + env(pay(sponsor2, bob, drops(1)), + Txflags(tfSponsorCreatedAccount), + Fee(XRP(1)), + Ter(tecNO_SPONSOR_PERMISSION)); + env.close(); + + BEAST_EXPECT(env.balance(sponsor2) == XRP(9999)); + + // to non-funded account / insufficient balance for reserve + env(pay(sponsor2, charlie, XRP(9999) - env.current()->fees().reserve + drops(1)), + Txflags(tfSponsorCreatedAccount), + Ter(tecUNFUNDED_PAYMENT)); + env.close(); + + // to non-funded account + auto const sponsor2BalanceBefore = env.balance(sponsor2); + env(pay(sponsor2, charlie, drops(1)), Txflags(tfSponsorCreatedAccount), Fee(XRP(1))); + env.close(); + + auto const charlieSle = env.le(keylet::account(charlie)); + BEAST_EXPECT(charlieSle->isFieldPresent(sfSponsor)); + BEAST_EXPECT(charlieSle->getAccountID(sfSponsor) == sponsor2.id()); + BEAST_EXPECT(sponsoredOwnerCount(env, charlie) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 1); + // verify sponsor balance decreased by payment + Fee + BEAST_EXPECT(env.balance(sponsor2) == sponsor2BalanceBefore - drops(1) - XRP(1)); + } + { + // insufficient reserve to sponsor acount + + auto const sendAmount = drops(1); + // 2 account reserve + send amount + auto const requireBalance = accountReserve(env, 2) + sendAmount; + adjustAccountXRPBalance(env, sponsor3, requireBalance - drops(1)); + env(pay(sponsor3, dave, sendAmount), + Txflags(tfSponsorCreatedAccount), + Fee(XRP(1)), + Ter(tecUNFUNDED_PAYMENT)); + env.close(); + + adjustAccountXRPBalance(env, sponsor3, requireBalance); + env(pay(sponsor3, dave, sendAmount), + Txflags(tfSponsorCreatedAccount), + Fee(XRP(1)), + Ter(tesSUCCESS)); + env.close(); + } + } + + void + testRequireFlag() + { + using namespace test::jtx; + { + testcase("SponsorshipRequireSignForReserve"); + + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + // set flag + env(sponsor::set_reserve(sponsor, tfSponsorshipSetRequireSignForReserve, 10), + sponsor::SponseeAcc(alice)); + env.close(); + + env(check::create(alice, bob, XRP(100)), + Fee(XRP(10)), + sponsor::As(sponsor, spfSponsorReserve), + Ter(terNO_PERMISSION)); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + + // clear flag + env(sponsor::set_reserve(sponsor, tfSponsorshipClearRequireSignForReserve, 1), + sponsor::SponseeAcc(alice)); + env.close(); + + // CheckCreate is re-applied + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + } + + { + testcase("SponsorshipRequireSignForFee"); + + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + // set flag + env(sponsor::set_fee(sponsor, tfSponsorshipSetRequireSignForFee, XRP(10)), + sponsor::SponseeAcc(alice)); + env.close(); + + env(check::create(alice, bob, XRP(100)), + Fee(XRP(10)), + sponsor::As(sponsor, spfSponsorFee), + Ter(terNO_PERMISSION)); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT( + env.le(keylet::sponsorship(sponsor, alice))->getFieldAmount(sfFeeAmount) == + XRP(10)); + + // clear flag + env(sponsor::set_fee(sponsor, tfSponsorshipClearRequireSignForFee, XRP(10)), + sponsor::SponseeAcc(alice)); + env.close(); + + // CheckCreate is re-applied + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice))->isFieldPresent(sfFeeAmount)); + } + } + + void + testSponsorReserveSimple(bool cosigning) + { + testcase("SponsorReserveSimple"); + using namespace test::jtx; + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + if (cosigning) + { + adjustAccountXRPBalance(env, sponsor, reserve(env, 1) - drops(1)); + + env(check::create(alice, bob, XRP(100)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + adjustAccountXRPBalance(env, sponsor, reserve(env, 1)); + + env(check::create(alice, bob, XRP(100)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tesSUCCESS)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor, 0, 250), sponsor::SponseeAcc(alice)); + env.close(); + + adjustAccountXRPBalance(env, sponsor, reserve(env, 2) - drops(1)); + + env(check::create(alice, bob, XRP(100)), + sponsor::As(sponsor, spfSponsorReserve), + Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + adjustAccountXRPBalance(env, sponsor, reserve(env, 2)); + + env(check::create(alice, bob, XRP(100)), + sponsor::As(sponsor, spfSponsorReserve), + Ter(tesSUCCESS)); + env.close(); + } + } + + // test helper for both cosigning and pre-funded sponsorship + template + void + testEachSponsorship( + test::jtx::Env& env, + bool cosigning, + jtx::Account const& sponsor, + jtx::Account const& sponsee, + uint32_t reserveCount, + uint32_t sponsorReserveCount, + TER insufficientReserveResult, + SubmitCallback callback, + std::optional> expected = std::nullopt) + { + using namespace test::jtx; + auto const sponseeOwnerCountBefore = ownerCount(env, sponsee); + auto const sponseeSponsoredOwnerCountBefore = sponsoredOwnerCount(env, sponsee); + auto const sponseeSponsoringOwnerCountBefore = sponsoringOwnerCount(env, sponsee); + auto const sponsorOwnerCountBefore = ownerCount(env, sponsor); + auto const sponsorSponsoredOwnerCountBefore = sponsoredOwnerCount(env, sponsor); + auto const sponsorSponsoringOwnerCountBefore = sponsoringOwnerCount(env, sponsor); + + std::optional sponsorSig = + cosigning ? std::optional(Sig(sfSponsorSignature, sponsor)) : std::nullopt; + + auto const sponsorCurrentOwnerCount = ownerCount(env, sponsor) - + sponsoredOwnerCount(env, sponsor) + sponsoringOwnerCount(env, sponsor); + + auto submit = [&](TER ter) { + return [&, ter](json::Value const& jv, auto const&... fN) { + if (sponsorSig) + { + env(jv, fN..., sponsor::As(sponsor, spfSponsorReserve), *sponsorSig, Ter(ter)); + } + else + { + env(jv, fN..., sponsor::As(sponsor, spfSponsorReserve), Ter(ter)); + } + }; + }; + + // Insufficient Reserve + { + if (cosigning) + { + adjustAccountXRPBalance( + env, + sponsor, + reserve(env, sponsorCurrentOwnerCount + sponsorReserveCount) - drops(1)); + } + else + { + // cleanup previous sponsorship + if (env.le(keylet::sponsorship(sponsor, sponsee))) + { + env(sponsor::del(sponsor), sponsor::SponseeAcc(sponsee)); + env.close(); + } + + if (sponsorReserveCount > 1) + { + env(sponsor::set(sponsor, 0, sponsorReserveCount - 1, XRP(1)), + sponsor::SponseeAcc(sponsee)); + } + else + { + // just create sponsor object + env(sponsor::set(sponsor, 0, std::nullopt, XRP(1)), + sponsor::SponseeAcc(sponsee)); + } + env.close(); + } + + // A failed sponsored create must not consume prefunded reserve or mutate owner counts. + auto const sponseeOwnerCountBeforeAttempt = ownerCount(env, sponsee); + auto const sponseeSponsoredOwnerCountBeforeAttempt = sponsoredOwnerCount(env, sponsee); + auto const sponseeSponsoringOwnerCountBeforeAttempt = + sponsoringOwnerCount(env, sponsee); + auto const sponsorOwnerCountBeforeAttempt = ownerCount(env, sponsor); + auto const sponsorSponsoredOwnerCountBeforeAttempt = sponsoredOwnerCount(env, sponsor); + auto const sponsorSponsoringOwnerCountBeforeAttempt = + sponsoringOwnerCount(env, sponsor); + auto const sponsorshipSleBeforeAttempt = env.le(keylet::sponsorship(sponsor, sponsee)); + bool const reserveCountPresentBeforeAttempt = sponsorshipSleBeforeAttempt && + sponsorshipSleBeforeAttempt->isFieldPresent(sfRemainingOwnerCount); + std::uint32_t const reserveCountBeforeAttempt = reserveCountPresentBeforeAttempt + ? sponsorshipSleBeforeAttempt->getFieldU32(sfRemainingOwnerCount) + : 0; + + callback(env, submit(insufficientReserveResult)); + env.close(); + + BEAST_EXPECT(ownerCount(env, sponsee) == sponseeOwnerCountBeforeAttempt); + BEAST_EXPECT( + sponsoredOwnerCount(env, sponsee) == sponseeSponsoredOwnerCountBeforeAttempt); + BEAST_EXPECT( + sponsoringOwnerCount(env, sponsee) == sponseeSponsoringOwnerCountBeforeAttempt); + BEAST_EXPECT(ownerCount(env, sponsor) == sponsorOwnerCountBeforeAttempt); + BEAST_EXPECT( + sponsoredOwnerCount(env, sponsor) == sponsorSponsoredOwnerCountBeforeAttempt); + BEAST_EXPECT( + sponsoringOwnerCount(env, sponsor) == sponsorSponsoringOwnerCountBeforeAttempt); + + auto const sponsorshipSleAfterAttempt = env.le(keylet::sponsorship(sponsor, sponsee)); + BEAST_EXPECT( + static_cast(sponsorshipSleAfterAttempt) == + static_cast(sponsorshipSleBeforeAttempt)); + if (sponsorshipSleAfterAttempt) + { + BEAST_EXPECT( + sponsorshipSleAfterAttempt->isFieldPresent(sfRemainingOwnerCount) == + reserveCountPresentBeforeAttempt); + if (reserveCountPresentBeforeAttempt) + { + BEAST_EXPECT( + sponsorshipSleAfterAttempt->getFieldU32(sfRemainingOwnerCount) == + reserveCountBeforeAttempt); + } + } + } + + // Success + { + if (cosigning) + { + adjustAccountXRPBalance( + env, sponsor, reserve(env, sponsorCurrentOwnerCount + sponsorReserveCount)); + } + else + { + // reset sponsorship + env(sponsor::del(sponsor), sponsor::SponseeAcc(sponsee)); + env(sponsor::set(sponsor, 0, sponsorReserveCount, XRP(1)), + sponsor::SponseeAcc(sponsee)); + env.close(); + } + callback(env, submit(tesSUCCESS)); + env.close(); + + if (!cosigning) + { + // Prefunded success consumes the reserved owner slot before cleanup. + auto const sponsorshipSle = env.le(keylet::sponsorship(sponsor, sponsee)); + BEAST_EXPECT(sponsorshipSle); + BEAST_EXPECT( + !sponsorshipSle->isFieldPresent(sfRemainingOwnerCount) || + sponsorshipSle->getFieldU32(sfRemainingOwnerCount) == 0); + + // cleanup sponsorship + env(sponsor::del(sponsor), sponsor::SponseeAcc(sponsee)); + env.close(); + } + } + + if (expected) + { + (*expected)(); + } + else + { + BEAST_EXPECT(ownerCount(env, sponsee) - sponseeOwnerCountBefore == reserveCount); + BEAST_EXPECT( + sponsoredOwnerCount(env, sponsee) - sponseeSponsoredOwnerCountBefore == + sponsorReserveCount); + BEAST_EXPECT( + sponsoringOwnerCount(env, sponsee) - sponseeSponsoringOwnerCountBefore == 0); + BEAST_EXPECT(ownerCount(env, sponsor) == sponsorOwnerCountBefore); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor) == sponsorSponsoredOwnerCountBefore); + BEAST_EXPECT( + sponsoringOwnerCount(env, sponsor) - sponsorSponsoringOwnerCountBefore == + sponsorReserveCount); + } + }; + + void + testCheck(bool cosigning) + { + testcase("Check"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const gw("gw"); + Account const sponsor("sponsor"); + Account const sponsor2("sponsor2"); + + auto const usd = gw["usd"]; + + { + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, bob, sponsor, sponsor2); + env.close(); + + // CheckCreate -> Check -> CheckCancel + + uint32_t seq = 0; + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + seq = env.seq(alice); + submit(check::create(alice, bob, XRP(1))); + }); + + BEAST_EXPECT(ownerCount(env, alice) == 1); // Check + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + auto const keylet = keylet::check(alice, seq); + BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor.id()); + + if (cosigning) + { + // transfer sponsor + env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice)); + env.close(); + + // transfer sponsor + env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + } + + BEAST_EXPECT(ownerCount(env, alice) == 1); // Check + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + + BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor2.id()); + + // CheckCancel + env(check::cancel(alice, keylet.key)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + } + + { + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + // CheckCreate -> CheckCash + uint32_t seq2 = 0; + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + seq2 = env.seq(alice); + submit(check::create(alice, bob, XRP(1))); + }); + + BEAST_EXPECT(ownerCount(env, bob) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0); + + // CheckCash + auto const checkId2 = keylet::check(alice, seq2).key; + env(check::cash(bob, checkId2, XRP(1))); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(ownerCount(env, bob) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + } + + // RippleState sponsor (CheckCashMakesTrustLine) + { + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, bob, gw, sponsor, sponsor2); + env.close(); + + env.trust(usd(100), alice); + env.close(); + env(pay(gw, alice, usd(100))); + env.close(); + + // CheckCreate -> CheckCash + uint32_t seq2 = 0; + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + seq2 = env.seq(alice); + submit(check::create(alice, bob, usd(1))); + }); + + BEAST_EXPECT(ownerCount(env, bob) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0); + + auto const keylet = keylet::check(alice, seq2); + BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor.id()); + + // CheckCash + testEachSponsorship( + env, + cosigning, + sponsor, + bob, + 1, + 1, + tecNO_LINE_INSUF_RESERVE, + [&](Env& env, auto const& submit) { submit(check::cash(bob, keylet.key, usd(1))); }, + [&]() { + BEAST_EXPECT(ownerCount(env, alice) == 1); // RippleState + BEAST_EXPECT(ownerCount(env, bob) == 1); // RippleState + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + // the newly-created trust line carries the sponsor on + // bob's side, and no sponsor on the counterparty side + bool const bobLow = bob.id() < gw.id(); + auto const lineSle = env.le(keylet::trustLine(bob, gw, usd.currency)); + BEAST_EXPECT(lineSle); + if (lineSle) + { + BEAST_EXPECT( + lineSle->getAccountID(bobLow ? sfLowSponsor : sfHighSponsor) == + sponsor.id()); + BEAST_EXPECT( + !lineSle->isFieldPresent(bobLow ? sfHighSponsor : sfLowSponsor)); + } + }); + } + + // MPT sponsor: cashing an MPT check creates a sponsored MPToken for + // the casher. Unlike the trust-line path, this returns + // tecINSUFFICIENT_RESERVE (not tecNO_LINE_INSUF_RESERVE) on shortfall. + { + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), bob, sponsor, sponsor2); + env.close(); + + MPTTester mptGw(env, gw, {.holders = {alice}}); + mptGw.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + auto const mpt = mptGw["MPT"]; + env(pay(gw, alice, mpt(10'000))); + env.close(); + + // CheckCreate (alice -> bob) paying the MPT + uint32_t seq2 = 0; + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + seq2 = env.seq(alice); + submit(check::create(alice, bob, mpt(1))); + }); + + auto const checkKeylet = keylet::check(alice, seq2); + BEAST_EXPECT(env.le(checkKeylet)->getAccountID(sfSponsor) == sponsor.id()); + BEAST_EXPECT(ownerCount(env, bob) == 0); + + // CheckCash by bob (no MPToken yet) creates a sponsored MPToken + testEachSponsorship( + env, + cosigning, + sponsor, + bob, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + submit(check::cash(bob, checkKeylet.key, mpt(1))); + }, + [&]() { + BEAST_EXPECT(ownerCount(env, bob) == 1); // MPToken + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + BEAST_EXPECT( + env.le(keylet::mptoken(mptGw.issuanceID(), bob))->getAccountID(sfSponsor) == + sponsor.id()); + }); + } + } + + void + testDelegate(bool cosigning) + { + testcase("Delegate"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + Account const sponsor2("sponsor2"); + + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, sponsor, sponsor2); + env.close(); + + // DelegateSet + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + submit(delegate::set(alice, bob, {"Payment"})); + }); + + // the created Delegate object carries the sponsor + auto const keylet = keylet::delegate(alice, bob); + BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor.id()); + + // transfer sponsor + if (cosigning) + { + env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice)); + env.close(); + + env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + } + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor2.id()); + + // delete + env(delegate::set(alice, bob, {})); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + } + } + + void + testDepositPreauth(bool cosigning) + { + testcase("DepositPreauth"); + using namespace test::jtx; + Account const alice("alice"); + Account const sponsor("sponsor"); + Account const sponsor2("sponsor2"); + auto const credType = std::string("credType"); + + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, sponsor, sponsor2); + env.close(); + + // DepositPreauthSet + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { submit(deposit::auth(alice, sponsor)); }); + + // the created DepositPreauth object carries the sponsor + auto const keylet = keylet::depositPreauth(alice, sponsor); + BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor.id()); + + // transfer sponsor + if (cosigning) + { + env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice)); + env.close(); + // No sponsor signature here: this exercises the prefunded reassign path. + env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + + auto const sponsor2Sle = env.le(keylet::sponsorship(sponsor2, alice)); + BEAST_EXPECT(sponsor2Sle); + if (sponsor2Sle) + { + BEAST_EXPECT( + !sponsor2Sle->isFieldPresent(sfRemainingOwnerCount) || + sponsor2Sle->getFieldU32(sfRemainingOwnerCount) == 0); + } + } + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor2.id()); + + // DepositPreauthDelete + env(deposit::unauth(alice, sponsor)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + } + + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, sponsor); + env.close(); + auto const authCreds = std::vector{ + {.issuer = sponsor, .credType = credType}}; + auto const preauthKeylet = keylet::depositPreauth( + alice.id(), + std::set>{ + {sponsor.id(), Slice(credType.data(), credType.size())}}); + + // Cover DepositPreauth's sfAuthorizeCredentials sponsor-reserve branch. + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env&, auto const& submit) { + submit(deposit::authCredentials(alice, authCreds)); + }); + + // Cover sfUnauthorizeCredentials cleanup for a sponsored preauth object. + BEAST_EXPECT(env.le(preauthKeylet)); + BEAST_EXPECT(env.le(preauthKeylet)->getAccountID(sfSponsor) == sponsor.id()); + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + env(deposit::unauthCredentials(alice, authCreds)); + env.close(); + + BEAST_EXPECT(!env.le(preauthKeylet)); + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + } + } + + void + testCredential(bool cosigning) + { + testcase("Credential"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + Account const sponsor2("sponsor2"); + auto const credType = std::string("test"); + + // Self-issued credential: alice creates for herself, sponsor covers reserve + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, sponsor, sponsor2); + env.close(); + + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + submit(credentials::create(alice, alice, credType)); + }); + + auto const credKeylet = credentials::keylet(alice, alice, credType); + BEAST_EXPECT(env.le(credKeylet)->getAccountID(sfSponsor) == sponsor.id()); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + // Transfer sponsor + if (cosigning) + { + env(sponsor::transfer(alice, tfSponsorshipReassign, credKeylet.key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice)); + env.close(); + env(sponsor::transfer(alice, tfSponsorshipReassign, credKeylet.key), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + } + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + BEAST_EXPECT(env.le(credKeylet)->getAccountID(sfSponsor) == sponsor2.id()); + + // CredentialDelete + env(credentials::deleteCred(alice, alice, alice, credType)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + } + + // Issuer creates for subject; sponsorship transfers to subject on accept + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, sponsor); + env.close(); + + // alice (issuer) creates credential for bob (subject), sponsor covers alice's reserve + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + submit(credentials::create(bob, alice, credType)); + }); + + // Before accept: alice (issuer) owns the credential + auto const credKeylet = credentials::keylet(bob, alice, credType); + BEAST_EXPECT(env.le(credKeylet)->getAccountID(sfSponsor) == sponsor.id()); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + BEAST_EXPECT(ownerCount(env, bob) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0); + + // Bob accepts: issuer's sponsorship ends, bob becomes unsponsored owner + env(credentials::accept(bob, alice, credType)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); // original sponsorship ended + BEAST_EXPECT(ownerCount(env, bob) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0); // bob owns it without a sponsor + + // CredentialDelete by subject + env(credentials::deleteCred(bob, bob, alice, credType)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(ownerCount(env, bob) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + } + + // Issuer creates for subject; accept tx itself is reserve-sponsored + // This exercises the checkReserve() call in CredentialAccept::doApply() + // that guards the sponsor's reserve when featureSponsor is enabled. + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, sponsor); + env.close(); + + // alice (issuer) creates credential for bob (subject) without sponsor + env(credentials::create(bob, alice, credType)); + env.close(); + + auto const credKeylet = credentials::keylet(bob, alice, credType); + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(ownerCount(env, bob) == 0); + + // Bob accepts with a sponsored reserve; the first attempt uses an + // undercapitalized sponsor and must fail with tecINSUFFICIENT_RESERVE; + // the second attempt uses a properly funded sponsor and must succeed. + testEachSponsorship( + env, + cosigning, + sponsor, + bob, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + submit(credentials::accept(bob, alice, credType)); + }); + + // After successful accept: alice (issuer) no longer owns the credential, + // bob owns it and sponsor covers his reserve. + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(ownerCount(env, bob) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + BEAST_EXPECT(env.le(credKeylet)->getAccountID(sfSponsor) == sponsor.id()); + + // Cleanup + env(credentials::deleteCred(bob, bob, alice, credType)); + env.close(); + } + } + + void + testEscrow(bool cosigning) + { + testcase("Escrow"); + using namespace test::jtx; + using namespace std::chrono_literals; + + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + Account const sponsor2("sponsor2"); + { + // Native Escrow + Env env{*this, testableAmendments()}; + auto const baseFee = env.current()->fees().base; + + env.fund(XRP(1000000), alice, bob, sponsor, sponsor2); + env.close(); + + // EscrowCreate + uint32_t seq = 0; + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + seq = env.seq(alice); + submit( + escrow::create(alice, bob, XRP(100)), + escrow::kCondition(escrow::kCb1), + escrow::kCancelTime(env.now() + 100s)); + }); + BEAST_EXPECT( + env.le(keylet::escrow(alice, seq))->getAccountID(sfSponsor) == sponsor.id()); + + // transfer sponsor + if (cosigning) + { + env(sponsor::transfer(alice, tfSponsorshipReassign, keylet::escrow(alice, seq).key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice)); + env.close(); + + env(sponsor::transfer(alice, tfSponsorshipReassign, keylet::escrow(alice, seq).key), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + } + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + + BEAST_EXPECT( + env.le(keylet::escrow(alice, seq))->getAccountID(sfSponsor) == sponsor2.id()); + + // EscrowFinish + env(escrow::finish(bob, alice, seq), + escrow::kCondition(escrow::kCb1), + escrow::kFulfillment(escrow::kFb1), + Fee(baseFee * 150)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + } + + Account const gw("gw"); + auto const usd = gw["usd"]; + { + // IOU Escrow + Env env{*this, testableAmendments()}; + auto const baseFee = env.current()->fees().base; + + env.fund(XRP(1000000), alice, bob, gw, sponsor, sponsor2); + env.close(); + + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + + env.trust(usd(1000000), alice); + env.close(); + env(pay(gw, alice, usd(10000))); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + + // EscrowCreate + uint32_t seq = 0; + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + seq = env.seq(alice); + submit( + escrow::create(alice, bob, usd(100)), + escrow::kCondition(escrow::kCb1), + escrow::kCancelTime(env.now() + 100s)); + }); + + BEAST_EXPECT( + env.le(keylet::escrow(alice, seq))->getAccountID(sfSponsor) == sponsor.id()); + + // EscrowFinish + testEachSponsorship( + env, + cosigning, + sponsor2, + bob, + 1, + 1, + tecNO_LINE_INSUF_RESERVE, + [&](Env& env, auto const& submit) { + submit( + escrow::finish(bob, alice, seq), + escrow::kCondition(escrow::kCb1), + escrow::kFulfillment(escrow::kFb1), + Fee(baseFee * 150)); + }); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + + BEAST_EXPECT( + env.le(keylet::trustLine(bob, gw, usd.currency))->getAccountID(sfHighSponsor) == + sponsor2.id()); + } + { + // IOU EscrowFinish recycles reserve when the same sponsor backs + // the escrow being removed and the destination line being created. + Env env{*this, testableAmendments()}; + auto const baseFee = env.current()->fees().base; + + env.fund(XRP(1000000), alice, bob, gw, sponsor); + env.close(); + + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + + env.trust(usd(1000000), alice); + env.close(); + env(pay(gw, alice, usd(10000))); + env.close(); + + uint32_t seq = 0; + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + seq = env.seq(alice); + submit( + escrow::create(alice, bob, usd(100)), + escrow::kCondition(escrow::kCb1), + escrow::kCancelTime(env.now() + 100s)); + }); + + BEAST_EXPECT( + env.le(keylet::escrow(alice, seq))->getAccountID(sfSponsor) == sponsor.id()); + + if (cosigning) + { + adjustAccountXRPBalance(env, sponsor, reserve(env, 1)); + } + else + { + env(sponsor::set_reserve(sponsor, 0, 1), sponsor::SponseeAcc(bob)); + env.close(); + } + + if (cosigning) + { + env(escrow::finish(bob, alice, seq), + escrow::kCondition(escrow::kCb1), + escrow::kFulfillment(escrow::kFb1), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Fee(baseFee * 150), + Ter(tesSUCCESS)); + } + else + { + env(escrow::finish(bob, alice, seq), + escrow::kCondition(escrow::kCb1), + escrow::kFulfillment(escrow::kFb1), + sponsor::As(sponsor, spfSponsorReserve), + Fee(baseFee * 150), + Ter(tesSUCCESS)); + } + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(ownerCount(env, bob) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + BEAST_EXPECT( + env.le(keylet::trustLine(bob, gw, usd.currency))->getAccountID(sfHighSponsor) == + sponsor.id()); + } + { + // IOU Escrow cancel re-creates the owner's trust line, and the + // cancel transaction's sponsor can cover that new line's reserve. + // Creating an IOU escrow moves the sender's balance to the issuer, + // so the sender can delete their now-zero trust line while the + // escrow is pending. Cancelling returns the funds, re-creating the + // line. + Env env{*this, testableAmendments()}; + + env.fund(XRP(1000000), alice, bob, gw, sponsor, sponsor2); + env.close(); + + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + + env.trust(usd(1000000), alice); + env.close(); + env(pay(gw, alice, usd(10000))); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + + // alice escrows her entire balance so the trust line can be removed + auto const cancelAfter = env.now() + 100s; + auto const seq = env.seq(alice); + env(escrow::create(alice, bob, usd(10000)), + escrow::kCondition(escrow::kCb1), + escrow::kCancelTime(cancelAfter)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 2); // trust line + escrow + + // alice deletes her now-zero trust line while the escrow is pending + env(trust(alice, usd(0))); + env.close(); + BEAST_EXPECT(!env.le(keylet::trustLine(alice, gw, usd.currency))); + BEAST_EXPECT(ownerCount(env, alice) == 1); // just the escrow + + // advance the ledger past the cancel time + for (; env.now() < cancelAfter; env.close()) + { + } + + // EscrowCancel by alice re-creates her trust line; the sponsor + // covers the new line's reserve. On the insufficient-reserve + // attempt the whole transaction rolls back, leaving the escrow + // intact to be cancelled on the success attempt. + bool const aliceLow = alice.id() < gw.id(); + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecNO_LINE_INSUF_RESERVE, + [&](Env& env, auto const& submit) { submit(escrow::cancel(alice, alice, seq)); }, + [&]() { + BEAST_EXPECT(!env.le(keylet::escrow(alice, seq))); + auto const trustSle = env.le(keylet::trustLine(alice, gw, usd.currency)); + BEAST_EXPECT(trustSle); + if (trustSle) + { + BEAST_EXPECT( + trustSle->getAccountID(aliceLow ? sfLowSponsor : sfHighSponsor) == + sponsor.id()); + BEAST_EXPECT( + !trustSle->isFieldPresent(aliceLow ? sfHighSponsor : sfLowSponsor)); + } + BEAST_EXPECT(ownerCount(env, alice) == 1); // re-created trust line + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + }); + } + { + // MPT Escrow + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), bob, sponsor); + env.close(); + + MPTTester mptGw(env, gw, {.holders = {alice}}); + mptGw.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + auto const mpt = mptGw["MPT"]; + env(pay(gw, alice, mpt(10'000))); + env.close(); + + // create Escrow from alice to bob + auto const seq = env.seq(alice); + env(escrow::create(alice, bob, mpt(100)), + escrow::kCondition(escrow::kCb1), + escrow::kCancelTime(env.now() + 100s)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 2); + BEAST_EXPECT(ownerCount(env, bob) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + + // finish Escrow: bob has no MPToken, so finishing creates one and + // the finish transaction's sponsor covers its reserve. The MPT + // create-token path returns tecINSUFFICIENT_RESERVE (not + // tecNO_LINE_INSUF_RESERVE) when the sponsor is underfunded. + testEachSponsorship( + env, + cosigning, + sponsor, + bob, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + submit( + escrow::finish(bob, alice, seq), + escrow::kCondition(escrow::kCb1), + escrow::kFulfillment(escrow::kFb1), + Fee(XRP(1))); + }); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(ownerCount(env, bob) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + BEAST_EXPECT( + env.le(keylet::mptoken(mptGw.issuanceID(), bob))->getAccountID(sfSponsor) == + sponsor.id()); + } + { + // MPT EscrowFinish has the same reserve recycling behavior as IOU + // when it creates the destination MPToken. + Env env{*this, testableAmendments()}; + auto const baseFee = env.current()->fees().base; + env.fund(XRP(1000000), bob, sponsor); + env.close(); + + MPTTester mptGw(env, gw, {.holders = {alice}}); + mptGw.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + auto const mpt = mptGw["MPT"]; + env(pay(gw, alice, mpt(10'000))); + env.close(); + + uint32_t seq = 0; + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + seq = env.seq(alice); + submit( + escrow::create(alice, bob, mpt(100)), + escrow::kCondition(escrow::kCb1), + escrow::kCancelTime(env.now() + 100s)); + }); + + BEAST_EXPECT( + env.le(keylet::escrow(alice, seq))->getAccountID(sfSponsor) == sponsor.id()); + + if (cosigning) + { + adjustAccountXRPBalance(env, sponsor, reserve(env, 1)); + } + else + { + env(sponsor::set_reserve(sponsor, 0, 1), sponsor::SponseeAcc(bob)); + env.close(); + } + + if (cosigning) + { + env(escrow::finish(bob, alice, seq), + escrow::kCondition(escrow::kCb1), + escrow::kFulfillment(escrow::kFb1), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Fee(baseFee * 150), + Ter(tesSUCCESS)); + } + else + { + env(escrow::finish(bob, alice, seq), + escrow::kCondition(escrow::kCb1), + escrow::kFulfillment(escrow::kFb1), + sponsor::As(sponsor, spfSponsorReserve), + Fee(baseFee * 150), + Ter(tesSUCCESS)); + } + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(ownerCount(env, bob) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + BEAST_EXPECT( + env.le(keylet::mptoken(mptGw.issuanceID(), bob))->getAccountID(sfSponsor) == + sponsor.id()); + } + + // A sponsored EscrowCreate must still verify that the source + // can fund the escrow amount and stay above its own base + // reserve. The sponsor covers the new object's owner + // increment, but cannot cover the source's base reserve. + { + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + // alice's balance is just above the base reserve. After + // locking escrowAmount she would dip below it. + adjustAccountXRPBalance(env, alice, accountReserve(env, 1) + XRP(1)); + + auto const escrowAmount = XRP(2); + auto const seq = env.seq(alice); + + if (cosigning) + { + env(escrow::create(alice, bob, escrowAmount), + escrow::kCondition(escrow::kCb1), + escrow::kCancelTime(env.now() + 100s), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecUNFUNDED)); + } + else + { + env(sponsor::set(sponsor, 0, 1, XRP(1)), sponsor::SponseeAcc(alice)); + env.close(); + + env(escrow::create(alice, bob, escrowAmount), + escrow::kCondition(escrow::kCb1), + escrow::kCancelTime(env.now() + 100s), + sponsor::As(sponsor, spfSponsorReserve), + Ter(tecUNFUNDED)); + } + env.close(); + + BEAST_EXPECT(!env.le(keylet::escrow(alice, seq))); + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + } + } + + void + testMPToken(bool cosigning) + { + testcase("MPToken"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + Account const sponsor2("sponsor2"); + + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, sponsor, sponsor2); + env.close(); + + // MPTokenIssuanceCreate + json::Value jv = {}; + jv[sfAccount] = alice.human(); + jv[sfTransactionType] = jss::MPTokenIssuanceCreate; + MPTID mptid; + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + mptid = makeMptID(env.seq(alice), alice.id()); + submit(jv); + }); + + // transfer sponsor + auto const mptIssuanceKeylet = keylet::mptokenIssuance(mptid); + + if (cosigning) + { + env(sponsor::transfer(alice, tfSponsorshipReassign, mptIssuanceKeylet.key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice)); + env.close(); + + env(sponsor::transfer(alice, tfSponsorshipReassign, mptIssuanceKeylet.key), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + } + + // MPTokenAuthorize + jv = {}; + jv[sfTransactionType] = jss::MPTokenAuthorize; + jv[sfAccount] = bob.human(); + jv[sfMPTokenIssuanceID] = to_string(mptid); + + if (cosigning) + { + adjustAccountXRPBalance(env, sponsor, reserve(env, 2)); + env(ticket::create(sponsor, 2)); // adjust for free mptoken + env.close(); + } + + testEachSponsorship( + env, + cosigning, + sponsor, + bob, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { submit(jv); }); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + + // transfer sponsor + auto const mptTokenKeylet = keylet::mptoken(mptid, bob); + if (cosigning) + { + env(sponsor::transfer(bob, tfSponsorshipReassign, mptTokenKeylet.key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(bob)); + env.close(); + + env(sponsor::transfer(bob, tfSponsorshipReassign, mptTokenKeylet.key), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + } + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(ownerCount(env, bob) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 2); + + // MPTokenAuthorize Unauthorize + jv = {}; + jv[sfTransactionType] = jss::MPTokenAuthorize; + jv[sfAccount] = bob.human(); + jv[sfMPTokenIssuanceID] = to_string(mptid); + jv[sfFlags] = tfMPTUnauthorize; + env(jv); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(ownerCount(env, bob) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + + // MPTokenIssuanceDestroy + jv = {}; + jv[sfTransactionType] = jss::MPTokenIssuanceDestroy; + jv[sfAccount] = alice.human(); + jv[sfMPTokenIssuanceID] = to_string(mptid); + env(jv); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + } + { + // check INSUFFICIENT_RESERVE for MPToken + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, sponsor); + env.close(); + + // MPTokenAuthorize + json::Value jv = {}; + jv[sfAccount] = alice.human(); + jv[sfTransactionType] = jss::MPTokenIssuanceCreate; + auto const mptid = makeMptID(env.seq(alice), alice.id()); + env(jv); + env.close(); + + // Create tickets so the sponsor is past free-tier reserve behavior. + std::uint32_t const ticketSeq{env.seq(sponsor) + 1}; + env(ticket::create(sponsor, 2)); + env.close(); + + jv = {}; + jv[sfTransactionType] = jss::MPTokenAuthorize; + jv[sfAccount] = bob.human(); + jv[sfMPTokenIssuanceID] = to_string(mptid); + // error (non-free mptoken) + if (cosigning) + { + adjustAccountXRPBalance(env, sponsor, reserve(env, 3) - drops(1)); + env(jv, + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + } + else + { + env(sponsor::set(sponsor, 0, std::nullopt, XRP(1)), sponsor::SponseeAcc(bob)); + env.close(); + + env(jv, sponsor::As(sponsor, spfSponsorReserve), Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + } + + env(noop(sponsor), ticket::Use(ticketSeq)); + env.close(); + + // pass (free-tier mptoken for the holder, but the sponsor is still + // charged a reserve increment regardless of the ownerCount < 2 shortcut). + if (cosigning) + { + adjustAccountXRPBalance(env, sponsor, reserve(env, 2)); + env(jv, + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tesSUCCESS)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor, 0, 1), sponsor::SponseeAcc(bob)); + env.close(); + env(jv, sponsor::As(sponsor, spfSponsorReserve), Ter(tesSUCCESS)); + env.close(); + } + } + } + + void + testPayChan(bool cosigning) + { + testcase("PayChan"); + using namespace test::jtx; + using namespace std::literals::chrono_literals; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + Account const sponsor2("sponsor2"); + + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, sponsor, sponsor2); + env.close(); + + // PayChanCreate + auto const pk = alice.pk(); + auto const settleDelay = 10s; + uint256 chan; + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + chan = paychan::channel(alice, bob, env.seq(alice)); + submit(paychan::create(alice, bob, XRP(100), settleDelay, pk)); + }); + + // the created PayChannel object carries the sponsor + BEAST_EXPECT(env.le(Keylet(ltPAYCHAN, chan))->getAccountID(sfSponsor) == sponsor.id()); + + // transfer sponsor + if (cosigning) + { + env(sponsor::transfer(alice, tfSponsorshipReassign, chan), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice)); + env.close(); + + env(sponsor::transfer(alice, tfSponsorshipReassign, chan), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + } + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + BEAST_EXPECT(env.le(Keylet(ltPAYCHAN, chan))->getAccountID(sfSponsor) == sponsor2.id()); + + env.close(env.now() + settleDelay); + // PayChanClaim (delete PayChan) + env(paychan::claim(bob, chan), Txflags(tfClose)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + } + + // A sponsored PaymentChannelCreate must still verify that the + // source can fund the channel amount and stay above its own + // base reserve. The sponsor covers the new object's owner + // increment, but cannot cover the source's base reserve. + { + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + // alice's balance is just above the base reserve. After + // locking channelAmount she would dip below it. + adjustAccountXRPBalance(env, alice, accountReserve(env, 1) + XRP(1)); + + auto const pk = alice.pk(); + auto const settleDelay = 10s; + auto const channelAmount = XRP(2); + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + + if (cosigning) + { + env(paychan::create(alice, bob, channelAmount, settleDelay, pk), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecUNFUNDED)); + } + else + { + env(sponsor::set(sponsor, 0, 1, XRP(1)), sponsor::SponseeAcc(alice)); + env.close(); + + env(paychan::create(alice, bob, channelAmount, settleDelay, pk), + sponsor::As(sponsor, spfSponsorReserve), + Ter(tecUNFUNDED)); + } + env.close(); + + BEAST_EXPECT(!paychan::channelExists(*env.current(), chan)); + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + } + } + + void + testSignerList(bool cosigning) + { + testcase("SignerList"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + Account const sponsor2("sponsor2"); + + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, sponsor, sponsor2); + env.close(); + + // SignerListSet + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { submit(signers(alice, 1, {{bob, 1}})); }); + + // the created SignerList object carries the sponsor + BEAST_EXPECT(env.le(keylet::signerList(alice))->getAccountID(sfSponsor) == sponsor.id()); + + // transfer sponsor + if (cosigning) + { + // invalid signer list owner 1 + // account doesn't have signer list but specified signer list exists + env(sponsor::transfer(bob, tfSponsorshipReassign, keylet::signerList(alice).key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2), + Ter(tecNO_PERMISSION)); + // invalid signer list owner 2 + // account has signer list and specified signer list exists + env(signers(bob, 1, {{alice, 1}})); + env.close(); + env(sponsor::transfer(alice, tfSponsorshipReassign, keylet::signerList(bob).key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2), + Ter(tecNO_PERMISSION)); + env(sponsor::transfer(alice, tfSponsorshipReassign, keylet::signerList(alice).key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice)); + env.close(); + env(sponsor::transfer(alice, tfSponsorshipReassign, keylet::signerList(alice).key), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + } + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + BEAST_EXPECT(env.le(keylet::signerList(alice))->getAccountID(sfSponsor) == sponsor2.id()); + + // Delete + env(signers(alice, NoneT())); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + } + + // Legacy (pre-MultiSignReserve) SignerLists lack lsfOneOwnerCount and cost + // 2 + signer_count owner units, whereas modern lists cost 1. + void + testLegacySignerListReserve() + { + testcase("Legacy SignerList sponsorship reserve"); + using namespace test::jtx; + + Account const alice("alice"); + Account const bob("bob"); + Account const carol("carol"); + Account const dave("dave"); + Account const sponsor("sponsor"); + + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, carol, dave, sponsor); + env.close(); + + // Modern 3-signer list: weight 1, lsfOneOwnerCount set. + env(signers(alice, 1, {{bob, 1}, {carol, 1}, {dave, 1}})); + env.close(); + + auto const signerListKeylet = keylet::signerList(alice.id()); + auto const sponsorKeylet = keylet::sponsorship(sponsor.id(), alice.id()); + std::uint32_t const legacyWeight = 5; // 2 + 3 signers + BEAST_EXPECT(ownerCount(env, alice) == 1); + + // Pre-fund exactly the legacy weight + env(sponsor::set_reserve(sponsor, 0, legacyWeight), sponsor::SponseeAcc(alice)); + env.close(); + if (auto const sle = env.le(sponsorKeylet); BEAST_EXPECT(sle)) + BEAST_EXPECT(sle->getFieldU32(sfRemainingOwnerCount) == legacyWeight); + + // Synthesize a pre-MultiSignReserve list: clear lsfOneOwnerCount and + // restore the owner's OwnerCount to the legacy weight. + env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal) -> bool { + auto signerList = std::make_shared(*view.read(signerListKeylet)); + auto account = std::make_shared(*view.read(keylet::account(alice.id()))); + signerList->clearFlag(lsfOneOwnerCount); + account->setFieldU32(sfOwnerCount, legacyWeight); + view.rawReplace(signerList); + view.rawReplace(account); + return true; + }); + if (auto const sle = env.le(signerListKeylet); BEAST_EXPECT(sle)) + BEAST_EXPECT((sle->getFlags() & lsfOneOwnerCount) == 0); + BEAST_EXPECT(ownerCount(env, alice) == legacyWeight); + + // Create must charge the full legacy weight (5), not 1: the bug bumped + // the counters by 1 and left 4 pre-funded units unspent. + env(sponsor::transfer(alice, tfSponsorshipCreate, signerListKeylet.key), + sponsor::As(sponsor, spfSponsorReserve)); + + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == legacyWeight); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == legacyWeight); + // All pre-funded units consumed (drained to absent). + if (auto const sle = env.le(sponsorKeylet); BEAST_EXPECT(sle)) + BEAST_EXPECT(!sle->isFieldPresent(sfRemainingOwnerCount)); + + // Delete unwinds the legacy weight; create bumped by the same amount, so + // the counters return to 0 (the bug bumped by 1 -> underflow on delete). + env(signers(alice, NoneT())); + + BEAST_EXPECT(!env.le(signerListKeylet)); + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + if (auto const sle = env.le(sponsorKeylet); BEAST_EXPECT(sle)) + BEAST_EXPECT(!sle->isFieldPresent(sfRemainingOwnerCount)); + } + + void + testSponsoredTrustLineNoFreeReserve() + { + // An account with ownerCount < 2 may create its first trust lines even + // without meeting the reserve. In any case, the sponsor pays the full + // reserve in all cases, even for the sponsee's very first trust line. + testcase("Sponsored trust line gets no free-reserve exception"); + using namespace test::jtx; + + Account const issuer("issuer"); + Account const alice("alice"); + Account const sponsor("sponsor"); + + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), issuer, alice, sponsor); + env.close(); + + auto const usd = issuer["usd"]; + auto const lineKeylet = keylet::trustLine(alice, issuer, usd.currency); + + // Sponsor funded for exactly its base reserve + adjustAccountXRPBalance(env, sponsor, reserve(env, 0)); + + // alice's ownerCount is 0, so an unsponsored first trust line would be + // free; but because it is sponsored, the reserve check is enforced + // against the sponsor, which is one increment short. + env(trust(alice, usd(100)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecNO_LINE_INSUF_RESERVE)); + env.close(); + + BEAST_EXPECT(!env.le(lineKeylet)); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + + // Give the sponsor has exactly one owner-reserve increment; the same + // sponsored first trust line now succeeds and the sponsor pays for it. + adjustAccountXRPBalance(env, sponsor, reserve(env, 1)); + + env(trust(alice, usd(100)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + BEAST_EXPECT(env.le(lineKeylet)); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(ownerCount(env, alice) == 1); + } + + void + testCoSignReserveBoundedBySponsorshipBudget() + { + // sponsor co-signs, so a fee-only object (ReserveCount == 0) makes a co-signed + // reserve sponsorship fail -- with no fallback to the sponsor's balance. + testcase("Co-signed reserve sponsorship is bounded by Sponsorship budget"); + using namespace test::jtx; + + Env env{*this, testableAmendments()}; + Account const sponsor("sponsor"); + Account const sponsee("sponsee"); + env.fund(XRP(10000), sponsor, sponsee); + env.close(); + + // Prefund a FEE-only Sponsorship for the sponsee; ReserveCount + // defaults to 0. + env(sponsor::set_fee(sponsor, 0, XRP(100)), sponsor::SponseeAcc(sponsee)); + env.close(); + BEAST_EXPECT(env.le(keylet::sponsorship(sponsor, sponsee))); + + // Sponsee creates a Check with the sponsor co-signing the reserve. The + // fee-only Sponsorship's has ReserveCount (0), so this fails + // with tecINSUFFICIENT_RESERVE + env(check::create(sponsee, sponsor, XRP(1)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + BEAST_EXPECT(ownerCount(env, sponsee) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsee) == 0); + + // Bumping the Sponsorship's ReserveCount budget makes the same + // co-signed reserve sponsorship succeed, the budget is what gates it. + env(sponsor::set_reserve(sponsor, 0, 1), sponsor::SponseeAcc(sponsee)); + env.close(); + + env(check::create(sponsee, sponsor, XRP(1)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(ownerCount(env, sponsee) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsee) == 1); + } + + void + testTrustSet(bool cosigning) + { + testcase("TrustSet"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const charlie("charlie"); + Account const sponsor("sponsor"); + Account const sponsor2("sponsor2"); + + auto const validateSponsoredTrustline = + [&](std::shared_ptr const& sle, bool isIssuerHigh, Account const& sponsor) { + BEAST_EXPECT( + sle->getAccountID(isIssuerHigh ? sfLowSponsor : sfHighSponsor) == sponsor.id()); + BEAST_EXPECT(!sle->isFieldPresent(isIssuerHigh ? sfHighSponsor : sfLowSponsor)); + }; + + auto const& highAcc = alice > bob ? alice : bob; + auto const& lowAcc = alice > bob ? bob : alice; + + // create and delete + for (bool const isIssuerHigh : {false, true}) + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, charlie, sponsor, sponsor2); + env.close(); + + auto const& issuer = isIssuerHigh ? highAcc : lowAcc; + auto const& user = isIssuerHigh ? lowAcc : highAcc; + + auto const usd = issuer["usd"]; + auto const currency = usd.currency; + + // create TrustLine + if (cosigning) + { + adjustAccountXRPBalance(env, sponsor, reserve(env, 2)); + env(ticket::create(sponsor, 2)); // adjust for free trustline + env.close(); + } + + testEachSponsorship( + env, + cosigning, + sponsor, + user, + 1, + 1, + tecNO_LINE_INSUF_RESERVE, + [&](Env& env, auto const& submit) { submit(trust(user, usd(100))); }); + + auto const keylet = keylet::trustLine(user, issuer, currency); + + if (cosigning) + { + // invalid owner + env(sponsor::transfer(charlie, tfSponsorshipReassign, keylet.key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2), + Ter(tecNO_PERMISSION)); + // invalid reserve owner + env(sponsor::transfer(issuer, tfSponsorshipReassign, keylet.key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2), + Ter(tecNO_PERMISSION)); + env(sponsor::transfer(user, tfSponsorshipReassign, keylet.key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(user)); + env.close(); + env(sponsor::transfer(user, tfSponsorshipReassign, keylet.key), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + } + + // delete TrustLine + env(trust(user, usd(0))); + env.close(); + + BEAST_EXPECT(ownerCount(env, user) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, user) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + + BEAST_EXPECT(!env.le(keylet)); + } + + // update + for (bool const isIssuerHigh : {false, true}) + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, sponsor, sponsor2); + env.close(); + + auto const& issuer = isIssuerHigh ? highAcc : lowAcc; + auto const& user = isIssuerHigh ? lowAcc : highAcc; + + auto const usd = issuer["usd"]; + auto const currency = usd.currency; + + // create TrustLine from issuer + env(trust(issuer, user["usd"](100))); + env.close(); + + BEAST_EXPECT(env.le(keylet::trustLine(user, issuer, currency))); + + if (cosigning) + { + adjustAccountXRPBalance(env, sponsor, reserve(env, 2)); + env(ticket::create(sponsor, 2)); // adjust for free trustline + env.close(); + } + + testEachSponsorship( + env, + cosigning, + sponsor, + user, + 1, + 1, + tecINSUF_RESERVE_LINE, + [&](Env& env, auto const& submit) { submit(trust(user, usd(100))); }); + + auto const line = env.le(keylet::trustLine(user, issuer, currency)); + validateSponsoredTrustline(line, isIssuerHigh, sponsor); + + // update TrustLine from user to clear reserve + env(trust(user, usd(0))); + env.close(); + + BEAST_EXPECT(ownerCount(env, user) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, user) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(env.le(keylet::trustLine(user, issuer, currency))); + + // remove TrustLine from issuer + env(trust(issuer, user["usd"](0))); + env.close(); + BEAST_EXPECT(!env.le(keylet::trustLine(user, issuer, currency))); + } + + // both High and Low sponsored + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, sponsor); + env.close(); + + // create TrustLines + env(trust(alice, bob["usd"](100)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + env(trust(bob, alice["usd"](100)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + auto sle = env.le(keylet::trustLine(alice, bob, alice["usd"].currency)); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->isFlag(lsfHighReserve)); + BEAST_EXPECT(sle->isFlag(lsfLowReserve)); + BEAST_EXPECT(sle->getAccountID(sfHighSponsor) == sponsor.id()); + BEAST_EXPECT(sle->getAccountID(sfLowSponsor) == sponsor.id()); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(ownerCount(env, bob) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 2); + + // clear TrustLines + env(trust(alice, bob["usd"](0))); + env.close(); + env(trust(bob, alice["usd"](0))); + env.close(); + + sle = env.le(keylet::trustLine(alice, bob, alice["usd"].currency)); + BEAST_EXPECT(!sle); + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(ownerCount(env, bob) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + } + } + + void + testAccountDelete() + { + testcase("AccountDelete"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + + { + // Delete Sponsor/Sponsee Account with ltSponsorship (tecHAS_OBLIGATIONS) + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, sponsor); + env.close(); + + // set sponsor + env(sponsor::set(sponsor, 0, 100, XRP(100)), + sponsor::SponseeAcc(alice), + Ter(tesSUCCESS)); + env.close(); + + incLgrSeqForAccDel(env, sponsor); + + auto const keylet = keylet::sponsorship(sponsor, alice); + auto const sponsorObj = env.le(keylet); + BEAST_EXPECT(sponsorObj); + + // AccountDelete + auto const requiredFee = drops(env.current()->fees().increment); + env(acctdelete(alice, bob), Fee(requiredFee), Ter(tecHAS_OBLIGATIONS)); + env(acctdelete(sponsor, bob), Fee(requiredFee), Ter(tecHAS_OBLIGATIONS)); + } + + { + // Delete SponsoredAccount + Env env{*this, testableAmendments()}; + env.memoize(alice); + env.fund(XRP(1000000), bob, sponsor); + env.close(); + + // create SponsoredAccount + env(pay(sponsor, alice, XRP(10000)), Txflags(tfSponsorCreatedAccount)); + env.close(); + + incLgrSeqForAccDel(env, alice); + + // AccountDelete: destination = non-sponsor + auto const requiredFee = drops(env.current()->fees().increment); + env(acctdelete(alice, bob), Fee(requiredFee), Ter(tecNO_SPONSOR_PERMISSION)); + + // The failed delete must leave the account sponsored by the original sponsor. + auto const aliceSle = env.le(keylet::account(alice)); + BEAST_EXPECT(aliceSle); + if (aliceSle) + BEAST_EXPECT(aliceSle->getAccountID(sfSponsor) == sponsor.id()); + + auto const sponsorSle = env.le(keylet::account(sponsor)); + BEAST_EXPECT(sponsorSle->getFieldU32(sfSponsoringAccountCount) == 1); + + incLgrSeqForAccDel(env, alice); + + // AccountDelete: destination = sponsor + env(acctdelete(alice, sponsor), Fee(requiredFee), Ter(tesSUCCESS)); + + auto const sponsorSle2 = env.le(keylet::account(sponsor)); + BEAST_EXPECT(!sponsorSle2->isFieldPresent(sfSponsoringAccountCount)); + } + + { + // Sponsor with sfSponsoringOwnerCount cannot delete (tecHAS_OBLIGATIONS) + Env env{*this, testableAmendments()}; + Account const gw("gw"); + env.fund(XRP(1000000), alice, bob, sponsor, gw); + env.close(); + + auto const usd = gw["usd"]; + + // Create a trust line for alice + env(trust(alice, usd(1000))); + env.close(); + + // Transfer reserve sponsorship of trust line to sponsor + auto const trustId = keylet::trustLine(alice, gw, usd.currency); + BEAST_EXPECT(env.le(trustId)); + + env(sponsor::transfer(alice, tfSponsorshipCreate, trustId.key), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + // Verify sfSponsoringOwnerCount is set on sponsor + auto const sponsorSle = env.le(keylet::account(sponsor)); + BEAST_EXPECT(sponsorSle->isFieldPresent(sfSponsoringOwnerCount)); + auto const sponsoringOwnerCount = sponsorSle->getFieldU32(sfSponsoringOwnerCount); + BEAST_EXPECT(sponsoringOwnerCount >= 1); + + incLgrSeqForAccDel(env, sponsor); + + // AccountDelete should fail + auto const requiredFee = drops(env.current()->fees().increment); + env(acctdelete(sponsor, bob), Fee(requiredFee), Ter(tecHAS_OBLIGATIONS)); + // The failed delete must not decrement the outstanding sponsored-object count. + auto const sponsorSleAfter = env.le(keylet::account(sponsor)); + BEAST_EXPECT(sponsorSleAfter->isFieldPresent(sfSponsoringOwnerCount)); + BEAST_EXPECT( + sponsorSleAfter->getFieldU32(sfSponsoringOwnerCount) == sponsoringOwnerCount); + } + + { + // Sponsor with sfSponsoringAccountCount cannot delete (tecHAS_OBLIGATIONS) + Env env{*this, testableAmendments()}; + env.memoize(alice); + env.fund(XRP(1000000), bob, sponsor); + env.close(); + + // Create SponsoredAccount (sets sfSponsoringAccountCount on sponsor) + env(pay(sponsor, alice, XRP(10000)), Txflags(tfSponsorCreatedAccount)); + env.close(); + + // Verify sfSponsoringAccountCount is set on sponsor + auto const sponsorSle = env.le(keylet::account(sponsor)); + BEAST_EXPECT(sponsorSle->isFieldPresent(sfSponsoringAccountCount)); + auto const sponsoringAccountCount = sponsorSle->getFieldU32(sfSponsoringAccountCount); + BEAST_EXPECT(sponsoringAccountCount == 1); + + incLgrSeqForAccDel(env, sponsor); + + // AccountDelete should fail + auto const requiredFee = drops(env.current()->fees().increment); + env(acctdelete(sponsor, bob), Fee(requiredFee), Ter(tecHAS_OBLIGATIONS)); + // The failed delete must not decrement the outstanding sponsored-account count. + auto const sponsorSleAfter = env.le(keylet::account(sponsor)); + BEAST_EXPECT(sponsorSleAfter->isFieldPresent(sfSponsoringAccountCount)); + BEAST_EXPECT( + sponsorSleAfter->getFieldU32(sfSponsoringAccountCount) == sponsoringAccountCount); + } + + { + // Account with sponsored objects should be deletable + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, sponsor); + env.close(); + + // Create sponsored delegate (a non-deletion-blocker) + env(deposit::auth(alice, bob), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + + { + auto const sponsorSle = env.le(keylet::account(sponsor)); + auto const aliceSle = env.le(keylet::account(alice)); + BEAST_EXPECT(sponsorSle->at(sfSponsoringOwnerCount) == 1); + BEAST_EXPECT(aliceSle->at(sfOwnerCount) == 1); + BEAST_EXPECT(aliceSle->at(sfSponsoredOwnerCount) == 1); + } + + incLgrSeqForAccDel(env, alice); + + // AccountDelete should succeed + { + auto const requiredFee = drops(env.current()->fees().increment); + env(acctdelete(alice, bob), Fee(requiredFee), Ter(tesSUCCESS)); + BEAST_EXPECT(!env.le(keylet::account(alice))); + auto const sponsorSle = env.le(keylet::account(sponsor)); + BEAST_EXPECT(sponsorSle->at(sfSponsoringOwnerCount) == 0); + } + } + } + + void + testDelegatePermission() + { + testcase("DelegatePermission"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const carol("carol"); + + // + // SponsorshipTransfer is not delegable. + // + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, carol); + env.close(); + + auto const seq = env.seq(alice); + env(check::create(alice, bob, XRP(1))); + env.close(); + + auto const keylet = keylet::check(alice, seq); + + env(sponsor::transfer(alice, tfSponsorshipCreate, keylet.key), + sponsor::As(bob, spfSponsorReserve), + Sig(sfSponsorSignature, bob), + delegate::As(carol), + Ter(temINVALID)); + + env(delegate::set(alice, carol, {"SponsorshipTransfer"}), Ter(temMALFORMED)); + } + // + // test send SponsorshipSet on other's behalf + // + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, carol); + env.close(); + + env(sponsor::set(alice, 0, 100, XRP(100)), + sponsor::SponseeAcc(bob), + delegate::As(carol), + Ter(terNO_DELEGATE_PERMISSION)); + + env(delegate::set(alice, carol, {"SponsorshipSet"})); + env.close(); + + env(sponsor::set(alice, 0, 100, XRP(100)), + sponsor::SponseeAcc(bob), + delegate::As(carol), + Ter(tesSUCCESS)); + env.close(); + } + } + + void + testDelegateBlockReserveSponsor() + { + testcase("Delegate Block Reserve Sponsor"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const carol("carol"); + Account const sponsor("sponsor"); + + // Co-signed reserve sponsorship is blocked for delegated transactions. + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, carol, sponsor); + env.close(); + + env(delegate::set(alice, bob, {"CheckCreate"})); + env.close(); + + env(check::create(alice, carol, XRP(1)), + delegate::As(bob), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(temINVALID)); + } + + // Pre-funded reserve sponsorship is blocked for delegated transactions. + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, carol, sponsor); + env.close(); + + env(delegate::set(alice, bob, {"CheckCreate"})); + env(sponsor::set_reserve(sponsor, 0, 1), sponsor::SponseeAcc(bob)); + env.close(); + + env(check::create(alice, carol, XRP(1)), + delegate::As(bob), + sponsor::As(sponsor, spfSponsorReserve), + Ter(temINVALID)); + } + } + + void + testDelegateSponsorFeePayer() + { + testcase("Delegate Sponsor Fee Payer"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const carol("carol"); + Account const sponsor("sponsor"); + + // Co-signing: the sponsor account pays the delegated transaction fee. + { + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, bob, carol, sponsor); + env.close(); + + env(delegate::set(alice, bob, {"Payment"})); + env.close(); + + auto const aliceBalance = env.balance(alice); + auto const bobBalance = env.balance(bob); + auto const carolBalance = env.balance(carol); + auto const sponsorBalance = env.balance(sponsor); + auto const sendAmt = XRP(100); + auto const feeAmt = XRP(10); + + env(pay(alice, carol, sendAmt), + delegate::As(bob), + Fee(feeAmt), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + BEAST_EXPECT(env.balance(alice) == aliceBalance - sendAmt); + BEAST_EXPECT(env.balance(bob) == bobBalance); + BEAST_EXPECT(env.balance(carol) == carolBalance + sendAmt); + // sponsor pays the fee + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance - feeAmt); + } + + // Pre-funded: the Sponsorship object for sponsorship(sponsor, delegate) + // pays the fee. + { + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, bob, carol, sponsor); + env.close(); + + env(delegate::set(alice, bob, {"Payment"})); + env(sponsor::set_fee(sponsor, 0, XRP(100)), sponsor::SponseeAcc(bob)); + env.close(); + + auto const aliceBalance = env.balance(alice); + auto const bobBalance = env.balance(bob); + auto const carolBalance = env.balance(carol); + auto const sponsorBalance = env.balance(sponsor); + auto const sponsorFee = sponsor::sponsorshipFeeBalance(env, sponsor, bob); + auto const sendAmt = XRP(100); + auto const feeAmt = XRP(10); + + // verify sponsorship(sponsor, alice) is not present, because we are testing + // sponsorship(sponsor, bob) will pay the fee. bob is sfDelegate. + BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice))); + + env(pay(alice, carol, sendAmt), + delegate::As(bob), + Fee(feeAmt), + sponsor::As(sponsor, spfSponsorFee)); + env.close(); + + BEAST_EXPECT(env.balance(alice) == aliceBalance - sendAmt); + BEAST_EXPECT(env.balance(bob) == bobBalance); + BEAST_EXPECT(env.balance(carol) == carolBalance + sendAmt); + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance); + // sponsorship(sponsor, bob) pays the fee, bob is sfDelegate + BEAST_EXPECT(sponsor::sponsorshipFeeBalance(env, sponsor, bob) == sponsorFee - feeAmt); + } + } + + void + testBatch() + { + testcase("Batch"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + + // + // outer transaction + // + { + // test outer transaction with co-signing sponsor + Env env{*this, testableAmendments()}; + env.fund(XRP(1000), alice, bob, sponsor); + env.close(); + + auto const seq = env.seq(alice); + env(batch::outer(alice, seq, XRP(1), tfAllOrNothing), + batch::Inner(noop(alice), seq + 1), + batch::Inner(ticket::create(alice, 1), seq + 2), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(tesSUCCESS)); + env.close(); + + // does not affect reserve + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + + // Fee is paid by sponsor + BEAST_EXPECT(env.balance(alice) == XRP(1000)); + BEAST_EXPECT(env.balance(sponsor) == XRP(1000 - 1)); + } + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000), alice, bob, sponsor); + env.close(); + + // spfSponsorReserve on outer Batch is rejected + for (auto const flags : {spfSponsorReserve | spfSponsorFee, spfSponsorReserve}) + { + auto const seq = env.seq(alice); + env(batch::outer(alice, seq, XRP(1), tfAllOrNothing), + batch::Inner(noop(alice), seq + 1), + batch::Inner(noop(alice), seq + 2), + sponsor::As(sponsor, flags), + Sig(sfSponsorSignature, sponsor), + Ter(temINVALID_FLAG)); + env.close(); + } + } + { + // test outer transaction with prefunded sponsor + Env env{*this, testableAmendments()}; + env.fund(XRP(1000), alice, bob); + env.fund(XRP(1001), sponsor); + env.close(); + + env(sponsor::set(sponsor, 0, 100, XRP(100)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tesSUCCESS)); + env.close(); + + auto const seq = env.seq(alice); + env(batch::outer(alice, seq, XRP(1), tfAllOrNothing), + batch::Inner(noop(alice), seq + 1), + batch::Inner(ticket::create(alice, 1), seq + 2), + sponsor::As(sponsor, spfSponsorFee), + Ter(tesSUCCESS)); + env.close(); + + // does not affect reserve + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + + // Fee is paid by sponsor object + BEAST_EXPECT(env.balance(alice) == XRP(1000)); + BEAST_EXPECT(env.balance(sponsor) == XRP(900)); + + auto const sponsorshipSle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sponsorshipSle); + BEAST_EXPECT(sponsorshipSle->at(sfFeeAmount) == XRP(100 - 1)); + BEAST_EXPECT(sponsorshipSle->at(sfRemainingOwnerCount) == 100); + } + // + // Inner transaction + // + { + // test invalid Inner transaction with co-signing sponsor + Account const signerAccount("signer"); + Env env{*this, testableAmendments()}; + env.fund(XRP(1000), alice, bob, sponsor, signerAccount); + env.close(); + + env(signers(sponsor, 1, {Signer(signerAccount, 1)})); + env.close(); + + { + auto jt = env.jtnofill( + noop(alice), + sponsor::As(sponsor, spfSponsorReserve | spfSponsorFee), + Sig(sfSponsorSignature, sponsor)); + jt.jv.removeMember(sfTxnSignature.jsonName); + + auto const seq = env.seq(alice); + // should fail because Inner transaction cannot include SponsorSignature with + // TxnSignature + BEAST_EXPECT(jt.jv[sfSponsorSignature.jsonName].isMember(sfTxnSignature.jsonName)); + env(batch::outer(alice, seq, XRP(1), tfAllOrNothing), + batch::Inner(jt.jv, seq + 1), + batch::Inner(ticket::create(alice, 1), seq + 2), + Ter(temBAD_SIGNATURE)); + } + + { + auto jt = env.jtnofill( + noop(alice), + sponsor::As(sponsor, spfSponsorReserve | spfSponsorFee), + Msig(sfSponsorSignature, sponsor, signerAccount)); + jt.jv.removeMember(sfTxnSignature.jsonName); + + auto const seq = env.seq(alice); + // should fail because Inner transaction cannot include SponsorSignature with + // Signers + BEAST_EXPECT(jt.jv[sfSponsorSignature.jsonName].isMember(sfSigners.jsonName)); + env(batch::outer(alice, seq, XRP(1), tfAllOrNothing), + batch::Inner(jt.jv, seq + 1), + batch::Inner(ticket::create(alice, 1), seq + 2), + Ter(temBAD_SIGNER)); + } + + { + auto jt = env.jtnofill( + noop(alice), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + jt.jv.removeMember(sfTxnSignature.jsonName); + jt.jv[sfSponsorSignature.jsonName].removeMember(sfTxnSignature.jsonName); + jt.jv[sfSponsorSignature.jsonName][sfSigningPubKey.jsonName] = ""; + + auto const seq = env.seq(alice); + // should fail because BatchSigners does not have signer for SponsorSignature + env(batch::outer(alice, seq, XRP(1), tfAllOrNothing), + batch::Inner(jt.jv, seq + 1), + batch::Inner(ticket::create(alice, 1), seq + 2), + Ter(temBAD_SIGNER)); + } + } + + { + // test Inner transaction with prefunded sponsor + Env env{*this, testableAmendments()}; + env.fund(XRP(1000), alice, bob); + env.fund(XRP(1001), sponsor); + env.close(); + + env(sponsor::set(sponsor, 0, 100, XRP(100)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT(env.balance(sponsor) == XRP(900)); + + auto jt = env.jtnofill( + check::create(alice, bob, XRP(1)), sponsor::As(sponsor, spfSponsorReserve)); + // remove txn signature since it is filled by env.jtnofill() + jt.jv.removeMember(jss::TxnSignature); + + auto const seq = env.seq(alice); + env(batch::outer(alice, seq, XRP(1), tfAllOrNothing), + batch::Inner(noop(alice), seq + 1), + batch::Inner(jt.jv, seq + 2), + Ter(tesSUCCESS)); + env.close(); + + // affect sponsor reserve + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + // Fee is paid by outer transaction originator (alice) + BEAST_EXPECT(env.balance(alice) == XRP(999)); + BEAST_EXPECT(env.balance(sponsor) == XRP(900)); + + // reserve count is decreased + auto const sponsorshipSle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sponsorshipSle); + BEAST_EXPECT(sponsorshipSle->at(sfFeeAmount) == XRP(100)); + BEAST_EXPECT(sponsorshipSle->at(sfRemainingOwnerCount) == 99); + } + + { + // test Inner transaction with co-signing sponsor + Env env{*this, testableAmendments()}; + env.fund(XRP(1000), alice, bob, sponsor); + env.close(); + + auto jt = env.jtnofill( + check::create(alice, bob, XRP(1)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + // remove txn signature since it is filled by env.jtnofill() + jt.jv.removeMember(sfTxnSignature.jsonName); + jt.jv[sfSponsorSignature.jsonName].removeMember(sfTxnSignature.jsonName); + jt.jv[sfSponsorSignature.jsonName][sfSigningPubKey.jsonName] = ""; + + auto const seq = env.seq(alice); + env(batch::outer(alice, seq, XRP(1), tfAllOrNothing), + batch::Inner(noop(alice), seq + 1), + batch::Inner(jt.jv, seq + 2), + batch::Sig(sponsor), + Ter(tesSUCCESS)); + env.close(); + + // affect sponsor reserve + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + // Fee is paid by outer transaction originator (alice) + BEAST_EXPECT(env.balance(alice) == XRP(999)); + BEAST_EXPECT(env.balance(sponsor) == XRP(1000)); + } + + // Inner tx with sfSponsor + spfSponsorFee (pre-funded) is rejected + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000), alice, bob, sponsor); + env.close(); + + // Create pre-funded sponsorship + env(sponsor::set(sponsor, 0, 0, XRP(1)), sponsor::SponseeAcc(alice), Fee(XRP(1))); + env.close(); + + auto const seq = env.seq(alice); + auto const batchFee = batch::calcBatchFee(env, 0, 2); + + // Create inner transaction with sponsor fee + auto innerPay = pay(alice, bob, XRP(1)); + innerPay[sfSponsor.jsonName] = sponsor.human(); + innerPay[sfSponsorFlags.jsonName] = static_cast(spfSponsorFee); + + // Should be rejected with temBAD_FEE + env(batch::outer(alice, seq, batchFee, tfAllOrNothing), + batch::Inner(innerPay, seq + 1), + batch::Inner(noop(alice), seq + 2), + Ter(temINVALID_FLAG)); + env.close(); + } + + // Inner tx with sfSponsor + spfSponsorFee (co-signed) is rejected + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000), alice, bob, sponsor); + env.close(); + + auto const seq = env.seq(alice); + auto const batchFee = batch::calcBatchFee(env, 1, 2); + + auto innerPay = pay(alice, bob, XRP(1)); + innerPay[sfSponsor.jsonName] = sponsor.human(); + innerPay[sfSponsorFlags.jsonName] = static_cast(spfSponsorFee); + + // Should be rejected with temBAD_FEE + env(batch::outer(alice, seq, batchFee, tfAllOrNothing), + batch::Inner(innerPay, seq + 1), + batch::Inner(noop(alice), seq + 2), + batch::Sig(sponsor), + Ter(temINVALID_FLAG)); + env.close(); + } + + // Inner tx with spfSponsorFee + spfSponsorReserve is rejected + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000), alice, bob, sponsor); + env.close(); + + auto const seq = env.seq(alice); + auto const batchFee = batch::calcBatchFee(env, 0, 2); + + auto innerTx = check::create(alice, bob, XRP(1)); + innerTx[sfSponsor.jsonName] = sponsor.human(); + innerTx[sfSponsorFlags.jsonName] = + static_cast(spfSponsorFee | spfSponsorReserve); + + // Should be rejected with temBAD_FEE (fee sponsorship check comes first) + env(batch::outer(alice, seq, batchFee, tfAllOrNothing), + batch::Inner(innerTx, seq + 1), + batch::Inner(noop(alice), seq + 2), + Ter(temINVALID_FLAG)); + env.close(); + } + + // Outer batch tx with sponsor fee is allowed + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000), alice, bob, sponsor); + env.close(); + + auto const seq = env.seq(bob); + auto const batchFee = batch::calcBatchFee(env, 0, 2); + + // Outer batch with sponsor fee should work fine + env(batch::outer(bob, seq, batchFee, tfAllOrNothing), + batch::Inner(noop(bob), seq + 1), + batch::Inner(noop(bob), seq + 2), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(tesSUCCESS)); + env.close(); + + // Sponsor paid the fee + BEAST_EXPECT(env.balance(bob) == XRP(1000)); + } + } + + // Verify that the central allow-list in preflight1Sponsor rejects + // spfSponsorReserve for transaction types that v1 does not permit. + void + testReserveSponsorGate() + { + testcase("Reserve sponsor allow-list gate"); + using namespace test::jtx; + + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + env(sponsor::set(sponsor, 0, 10, XRP(10)), sponsor::SponseeAcc(alice)); + env.close(); + + auto checkBlocked = [&](json::Value const& jv) { + env(jv, + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(temINVALID_FLAG)); + }; + + checkBlocked(ticket::create(alice, 1)); + checkBlocked(offer(alice, XRP(100), bob["USD"](100))); + checkBlocked(did::setValid(alice)); + checkBlocked(token::mint(alice, 0u)); + checkBlocked(sponsor::set(alice, 0, 10, XRP(10))); + checkBlocked(acctdelete(alice, bob)); + checkBlocked(loan::set(alice, uint256(1), Number{1})); + } + + void + testSponsorReserve(bool cosigning) + { + testRequireFlag(); + testSponsorReserveSimple(cosigning); + testCheck(cosigning); + testCredential(cosigning); + testDelegate(cosigning); + testDepositPreauth(cosigning); + testEscrow(cosigning); + testMPToken(cosigning); + testPayChan(cosigning); + testSignerList(cosigning); + testTrustSet(cosigning); + } + + void + testZeroBalanceSponsoredPaymentFeePayerCheck() + { + // Zero-balance sponsored Payment: getFeePayer() consistency check + testcase("Sponsored Payment: minimal-balance account with sponsor-pays-fee"); + + using namespace jtx; + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const sponsor("sponsor"); + Account const dest("dest"); + + auto const baseFee = env.current()->fees().base; + auto const baseReserve = env.current()->fees().reserve; + + // Fund sponsor and dest generously, alice with base reserve + 1 XRP for payment + env.fund(XRP(10000), sponsor, dest); + env.fund(baseReserve + XRP(1), alice); + env.close(); + + // Precondition: alice has base reserve + 1 XRP (enough for payment but not fee) + BEAST_EXPECT(env.balance(alice) == baseReserve + XRP(1)); + + // Alice tries to send a Payment to dest where sponsor pays the fee via spfSponsorFee. + // Passed even alice balance doesn't have enough to pay fee. + auto const preDest = env.balance(dest); + auto const preSponsor = env.balance(sponsor); + + // Alice sends 1 XRP to dest, sponsor pays the fee + env(pay(alice, dest, XRP(1)), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Fee(baseFee)); + env.close(); + + // Payment succeeded + // Alice's balance decreased by 1 XRP (the payment amount, NOT the fee) + BEAST_EXPECT(env.balance(alice) == baseReserve); + + // Dest received 1 XRP + BEAST_EXPECT(env.balance(dest) == preDest + XRP(1)); + + // Sponsor paid the fee (NOT alice) + BEAST_EXPECT(env.balance(sponsor) == preSponsor - baseFee); + } + + void + testTrustSetCounterpartySponsorMisroute() + { + // TrustSet's modify path applies the tx-level reserve sponsor to whichever + // side has its reserve gate trip on this update, regardless of whether that + // side belongs to the tx submitter. trustCreate only sets the submitter's + // reserve flag and snapshots the counterparty's asfDefaultRipple state into + // the line's NoRipple bit; if the counterparty later toggles asfDefaultRipple + // (the canonical issuer flow), the line and account flags disagree and on + // the submitter's next TrustSet the counterparty-side gate fires. Sponsor still will be + // checked if it can be applied to that end of the trustLine. + + testcase("TrustSet modify with sponsor does not misroute onto counterparty side"); + + using namespace test::jtx; + + Env env{*this, testableAmendments()}; + Account const alice{"alice_t2178"}; + Account const bob{"bob_t2178"}; + Account const carol{"carol_t2178"}; + + // Fund without auto-setting asfDefaultRipple + env.fund(XRP(100'000), alice, bob, carol); + env.close(); + + // Determine account ordering + bool const aliceIsHigh = alice.id() > bob.id(); + + // To trigger the bug, we need the COUNTERPARTY's reserve gate to trip + // We use issuer/holder terminology where: + // - holder creates the trust line (their reserve is set first) + // - issuer enables DefaultRipple after (creates flag mismatch) + // - holder's second TrustSet triggers issuer's reserve gate + + auto const issuer = aliceIsHigh ? bob : alice; + auto const holder = aliceIsHigh ? alice : bob; + auto const usd = issuer["USD"]; + + // Issuer must NOT have DefaultRipple set initially + // Clear it explicitly (env.fund may have set it) + env(fclear(issuer, asfDefaultRipple)); + env.close(); + + // Holder creates the trust line first (holder's reserve flag is set) + // At this point, issuer does NOT have DefaultRipple set, so + // the NoRipple bit on issuer's side is set according to issuer's current flag + env(trust(holder, usd(1'000))); + env.close(); + + // Issuer now enables asfDefaultRipple (canonical issuer flow) + // This creates a mismatch: issuer's account flag says DefaultRipple=true + // but the trust line's NoRipple bit on issuer's side is still set + env(fset(issuer, asfDefaultRipple)); + env.close(); + + SF_ACCOUNT const& issuerSponsorField = aliceIsHigh ? sfLowSponsor : sfHighSponsor; + SF_ACCOUNT const& holderSponsorField = aliceIsHigh ? sfHighSponsor : sfLowSponsor; + + auto const lineKey = keylet::trustLine(alice, bob, usd.currency); + auto const sleLineBefore = env.le(lineKey); + if (!BEAST_EXPECT(sleLineBefore)) + return; + BEAST_EXPECT(!sleLineBefore->isFieldPresent(sfLowSponsor)); + BEAST_EXPECT(!sleLineBefore->isFieldPresent(sfHighSponsor)); + + auto const carolBefore = sponsoringOwnerCount(env, carol); + BEAST_EXPECT(carolBefore == 0); + auto const issuerSponsoredBefore = sponsoredOwnerCount(env, issuer); + BEAST_EXPECT(issuerSponsoredBefore == 0); + + // Holder modifies the trust line with Carol as sponsor + // This should trigger the issuer's reserve gate because of the DefaultRipple mismatch + // Carol (sponsor) should NOT be applied to issuer's side (issuer != tx submitter) + env(trust(holder, usd(2'000)), + sponsor::As(carol, spfSponsorReserve), + Sig(sfSponsorSignature, carol), + Ter(tesSUCCESS)); + env.close(); + + auto const sleLineAfter = env.le(lineKey); + if (!BEAST_EXPECT(sleLineAfter)) + return; + + // Carol only agreed to back the holder, not the issuer + BEAST_EXPECT(!sleLineAfter->isFieldPresent(issuerSponsorField)); + + // Holder's side also has no sponsor because holder's reserve flag was + // already set on the FIRST TrustSet (no sponsor in scope then) + BEAST_EXPECT(!sleLineAfter->isFieldPresent(holderSponsorField)); + + // Carol's sponsoring count should remain unchanged (no misroute) + auto const carolAfter = sponsoringOwnerCount(env, carol); + BEAST_EXPECT(carolAfter == carolBefore); + + // Issuer's sponsored count should remain unchanged (no misroute) + auto const issuerSponsoredAfter = sponsoredOwnerCount(env, issuer); + BEAST_EXPECT(issuerSponsoredAfter == issuerSponsoredBefore); + } + + void + testSelfEscrowFinishReserveGate() + { + testcase("Self-escrow finish reserve order gated by amendment"); + using namespace test::jtx; + using namespace std::chrono_literals; + + // Finishing a self-escrow (source == destination) whose trust line + // was deleted while the escrow was outstanding auto-creates the line, + // and the outcome of that reserve check depends on whether the escrow + // reserve is released before delivery (Sponsor) or after (legacy). + // With the source's balance in the one-increment window + // [reserve(1), reserve(2)), the legacy order requires reserve(2) and + // fails, while the Sponsor order requires reserve(1) and succeeds. + auto runTest = [&](FeatureBitset features, TER expected) { + Account const alice("alice"); + Account const gw("gw"); + auto const usd = gw["usd"]; + + Env env{*this, features}; + auto const baseFee = env.current()->fees().base; + + env.fund(XRP(10000), alice, gw); + env.close(); + + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + + env.trust(usd(1000), alice); + env.close(); + env(pay(gw, alice, usd(100))); + env.close(); + + // Escrow alice's entire USD balance to herself. The escrowed + // IOUs return to the issuer, zeroing the line balance. + auto const seq = env.seq(alice); + env(escrow::create(alice, alice, usd(100)), + escrow::kCondition(escrow::kCb1), + escrow::kCancelTime(env.now() + 100s)); + env.close(); + + // Delete the now-empty trust line. Both accounts have + // DefaultRipple set (jtx fund does that), so a plain limit-0 + // TrustSet returns the line to its default state. + env(trust(alice, usd(0))); + env.close(); + BEAST_EXPECT(!env.le(keylet::trustLine(alice, gw, usd.currency))); + BEAST_EXPECT(ownerCount(env, alice) == 1); // just the escrow + + // Put alice's balance in the window. Pay the excess away + // directly: adjustAccountXRPBalance needs the Sponsor amendment. + STAmount const target = reserve(env, 1) + XRP(1); + env(pay(alice, env.master, env.balance(alice) - target - baseFee), Fee(baseFee)); + env.close(); + BEAST_EXPECT(env.balance(alice) == target); + + env(escrow::finish(alice, alice, seq), + escrow::kCondition(escrow::kCb1), + escrow::kFulfillment(escrow::kFb1), + Fee(baseFee * 150), + Ter(expected)); + env.close(); + + if (expected == tesSUCCESS) + { + BEAST_EXPECT(!env.le(keylet::escrow(alice, seq))); + BEAST_EXPECT(env.le(keylet::trustLine(alice, gw, usd.currency))); + BEAST_EXPECT(env.balance(alice, usd) == usd(100)); + BEAST_EXPECT(ownerCount(env, alice) == 1); // the new line + } + else + { + BEAST_EXPECT(env.le(keylet::escrow(alice, seq))); + BEAST_EXPECT(!env.le(keylet::trustLine(alice, gw, usd.currency))); + BEAST_EXPECT(ownerCount(env, alice) == 1); // still the escrow + } + }; + + // Pre-amendment: legacy order — the escrow still counts against the + // reserve while the auto-created line is checked. + runTest(testableAmendments() - featureSponsor, tecNO_LINE_INSUF_RESERVE); + + // Post-amendment: the escrow reserve is recycled into the new line. + runTest(testableAmendments(), tesSUCCESS); + } + + void + testFeeSponsoredVaultInvariant() + { + // The ValidVault invariant checks that the vault's balance and the + // depositor's balance change by equal amounts. For XRP vaults it adds the + // fee back into the depositor's balance change: normally the depositor + // pays the fee, so their balance drops by (deposit amount + fee), and + // adding the fee back leaves just the deposit amount to compare against + // the vault. But when a fee sponsor pays, the depositor's balance drops + // by only the deposit amount, so the fee must NOT be added back or the + // equal-amount check fails. + testcase("Fee-sponsored VaultDeposit/VaultWithdraw pass ValidVault invariant"); + using namespace test::jtx; + + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, sponsor); + env.close(); + + PrettyAsset const xrpAsset{xrpIssue(), 1'000'000}; + Vault const vault{env}; + auto [vaultTx, vaultKeylet] = vault.create({.owner = alice, .asset = xrpAsset}); + env(vaultTx); + env.close(); + + // Control: the same deposit shape, unsponsored, succeeds. + env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = xrpAsset(100)}), + Ter(tesSUCCESS)); + env.close(); + + // Fee-sponsored (co-signed) deposit succeeds. + env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = xrpAsset(100)}), + Fee(XRP(1)), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(tesSUCCESS)); + env.close(); + + // The same helper (deltaAssetsTxAccount) drives the withdraw path, so a + // fee-sponsored withdrawal back to the depositor's own account also + // passes on the destination side. + env(vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = xrpAsset(50)}), + Fee(XRP(1)), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(tesSUCCESS)); + env.close(); + } + + void + testSponsoredObjectDeletionRefund() + { + // Deleting a co-signed reserve-sponsored object must + // refund the sponsor's SponsoringOwnerCount back to zero. + testcase("Sponsored object deletion refunds sponsor owner count"); + using namespace test::jtx; + + Env env{*this, testableAmendments()}; + Account const sponsor("sponsor"); + env.fund(XRP(100000), sponsor); + env.close(); + + // Sponsored Check deletion - verify decreaseOwnerCountForObject + // (used by CheckCancel) reads the object's sfSponsor field and refunds + // the sponsor's owner count. + { + testcase(" — Check deletion"); + + Account const checkOwner("check_owner"); + Account const dest("check_dest"); + env.fund(XRP(100000), checkOwner, dest); + env.close(); + + // Create a check with co-signed reserve sponsorship. This bumps the + // sponsor's sfSponsoringOwnerCount rather than the check owner's. + auto const checkSeq = env.seq(checkOwner); + env(check::create(checkOwner, dest, XRP(1)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + auto sponsorCountBefore = sponsoringOwnerCount(env, sponsor); + BEAST_EXPECT(sponsorCountBefore == 1); // check costs 1 owner count + + // Cancel (delete) the check. + env(check::cancel(checkOwner, keylet::check(checkOwner, checkSeq).key)); + env.close(); + + auto sponsorCountAfter = sponsoringOwnerCount(env, sponsor); + BEAST_EXPECT(sponsorCountAfter == 0); // fully refunded + } + + // Sponsored TrustSet (trust line) deletion - verify the sponsor + // refund works when a sponsored trust line is deleted + { + testcase(" — TrustLine deletion"); + + Account const issuer("issuer"); + Account const holder("holder"); + env.fund(XRP(100000), issuer, holder); + env.close(); + + auto const usd = issuer["USD"]; + + // Create trust line with sponsorship on the holder side + env(trust(holder, usd(1000)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + auto sponsorCountBefore = sponsoringOwnerCount(env, sponsor); + BEAST_EXPECT(sponsorCountBefore == 1); // trust line costs 1 + + // Delete the trust line by clearing it to default + env(trust(holder, usd(0))); + env.close(); + + auto sponsorCountAfter = sponsoringOwnerCount(env, sponsor); + BEAST_EXPECT(sponsorCountAfter == 0); // fully refunded + } + } + +protected: + void + testSponsor() + { + testDisabled(); + testInvalidSponsorshipSet(); + testPseudoAccountSponsorship(); + + testSingleSigning(); + testMultiSigning(); + + testInvalidSponsorField(); + + testSimpleSponsorshipSet(); + + testPreFundAndCosign(); + testSponsoredFreeTierReserve(); + + testTransferSponsor(); + testLegacySignerListReserve(); + testSponsorFee(); + testSponsorAccount(); + + testAccountDelete(); + + testDelegatePermission(); + testDelegateBlockReserveSponsor(); + testDelegateSponsorFeePayer(); + + testBatch(); + + testSponsoredTrustLineNoFreeReserve(); + testCoSignReserveBoundedBySponsorshipBudget(); + testReserveSponsorGate(); + + testZeroBalanceSponsoredPaymentFeePayerCheck(); + testTrustSetCounterpartySponsorMisroute(); + testSelfEscrowFinishReserveGate(); + + testFeeSponsoredVaultInvariant(); + testSponsoredObjectDeletionRefund(); + } + + void + testTxSponsor(bool cosigning) + { + testSponsorReserve(cosigning); + } + +public: + void + run() override + { + testSponsor(); + } +}; + +class SponsorTxCosigning_test : public Sponsor_test +{ + void + run() override + { + testTxSponsor(true); + } +}; + +class SponsorTxPrefunded_test : public Sponsor_test +{ + void + run() override + { + testTxSponsor(false); + } +}; + +BEAST_DEFINE_TESTSUITE(Sponsor, app, xrpl); +BEAST_DEFINE_TESTSUITE(SponsorTxCosigning, app, xrpl); +BEAST_DEFINE_TESTSUITE(SponsorTxPrefunded, app, xrpl); + +} // namespace xrpl::test diff --git a/src/test/app/Ticket_test.cpp b/src/test/app/Ticket_test.cpp index 626cdbe44d..f14afd5990 100644 --- a/src/test/app/Ticket_test.cpp +++ b/src/test/app/Ticket_test.cpp @@ -47,9 +47,11 @@ namespace xrpl { class Ticket_test : public beast::unit_test::Suite { - /// @brief Validate metadata for a successful TicketCreate transaction. - /// - /// @param env current jtx env (tx and meta are extracted using it) + /** + * @brief Validate metadata for a successful TicketCreate transaction. + * + * @param env current jtx env (tx and meta are extracted using it) + */ void checkTicketCreateMeta(test::jtx::Env& env) { @@ -233,11 +235,13 @@ class Ticket_test : public beast::unit_test::Suite BEAST_EXPECT(*ticketSeqs.rbegin() == acctRootFinalSeq - 1); } - /// @brief Validate metadata for a ticket using transaction. - /// - /// The transaction may have been successful or failed with a tec. - /// - /// @param env current jtx env (tx and meta are extracted using it) + /** + * @brief Validate metadata for a ticket using transaction. + * + * The transaction may have been successful or failed with a tec. + * + * @param env current jtx env (tx and meta are extracted using it) + */ void checkTicketConsumeMeta(test::jtx::Env& env) { @@ -503,7 +507,7 @@ class Ticket_test : public beast::unit_test::Suite Account const alice{"alice"}; // Fund alice not quite enough to make the reserve for a Ticket. - env.fund(env.current()->fees().accountReserve(1) - drops(1), alice); + env.fund(env.current()->fees().accountReserve(1, 1) - drops(1), alice); env.close(); env(ticket::create(alice, 1), Ter(tecINSUFFICIENT_RESERVE)); @@ -511,7 +515,8 @@ class Ticket_test : public beast::unit_test::Suite env.require(Owners(alice, 0), tickets(alice, 0)); // Give alice enough to exactly meet the reserve for one Ticket. - env(pay(env.master, alice, env.current()->fees().accountReserve(1) - env.balance(alice))); + env(pay( + env.master, alice, env.current()->fees().accountReserve(1, 1) - env.balance(alice))); env.close(); env(ticket::create(alice, 1)); @@ -524,7 +529,7 @@ class Ticket_test : public beast::unit_test::Suite env( pay(env.master, alice, - env.current()->fees().accountReserve(250) - drops(1) - env.balance(alice))); + env.current()->fees().accountReserve(250, 1) - drops(1) - env.balance(alice))); env.close(); // alice doesn't quite have the reserve for a total of 250 @@ -535,7 +540,8 @@ class Ticket_test : public beast::unit_test::Suite // Give alice enough so she can make the reserve for all 250 // Tickets. - env(pay(env.master, alice, env.current()->fees().accountReserve(250) - env.balance(alice))); + env(pay( + env.master, alice, env.current()->fees().accountReserve(250, 1) - env.balance(alice))); env.close(); std::uint32_t const ticketSeq{env.seq(alice) + 1}; diff --git a/src/test/app/TrustSet_test.cpp b/src/test/app/TrustSet_test.cpp index 15abc3bd06..e24373fd21 100644 --- a/src/test/app/TrustSet_test.cpp +++ b/src/test/app/TrustSet_test.cpp @@ -191,7 +191,7 @@ public: auto const txFee = env.current()->fees().base; auto const baseReserve = env.current()->fees().reserve; - auto const threelineReserve = env.current()->fees().accountReserve(3); + auto const threelineReserve = env.current()->fees().accountReserve(3, 1); env.fund(XRP(10000), gwA, gwB, assistor); diff --git a/src/test/app/TxQ_test.cpp b/src/test/app/TxQ_test.cpp index 91eb26da7e..3175e742d9 100644 --- a/src/test/app/TxQ_test.cpp +++ b/src/test/app/TxQ_test.cpp @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include #include #include @@ -2335,6 +2337,43 @@ public: BEAST_EXPECT(env.balance(alice) == drops(5)); } + void + testSponsorTxCannotQueue() + { + using namespace jtx; + testcase("disallow sponsored transaction from being queued"); + + Env env(*this, makeConfig({{Keys::kMinimumTxnInLedgerStandalone, "3"}})); + + auto sponsor = Account("sponsor"); + auto sponsee = Account("sponsee"); + auto filler = Account("filler"); + + env.fund(XRP(50000), noripple(sponsor, sponsee)); + env.close(); + env.fund(XRP(50000), noripple(filler)); + env.close(); + + fillQueue(env, filler); + checkMetrics(*this, env, 0, 6, 4, 3); + + // Sponsored transactions are not allowed to be queued. + env(noop(sponsee), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(telCAN_NOT_QUEUE)); + checkMetrics(*this, env, 0, 6, 4, 3); + + // Sponsored transactions may still apply directly if they pay the + // open ledger fee. They just cannot be held in the queue. + env(noop(sponsee), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Fee(openLedgerCost(env)), + Ter(tesSUCCESS)); + checkMetrics(*this, env, 0, 6, 5, 3); + } + void testDelegateTxCannotQueue() { @@ -4698,6 +4737,7 @@ public: testBlockersSeq(); testBlockersTicket(); testInFlightBalance(); + testSponsorTxCannotQueue(); testDelegateTxCannotQueue(); testConsequences(); } diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index 3877e08f7e..4de8c9c616 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -1804,7 +1805,7 @@ class Vault_test : public beast::unit_test::Suite auto const [acctReserve, incReserve] = [this]() -> std::pair { Env const env{*this, testableAmendments()}; return { - env.current()->fees().accountReserve(0).drops() / kDropsPerXrp.drops(), + env.current()->fees().accountReserve(0, 1).drops() / kDropsPerXrp.drops(), env.current()->fees().increment.drops() / kDropsPerXrp.drops()}; }(); @@ -2686,7 +2687,7 @@ class Vault_test : public beast::unit_test::Suite auto const [acctReserve, incReserve] = [this]() -> std::pair { Env const env{*this, testableAmendments()}; return { - env.current()->fees().accountReserve(0).drops() / kDropsPerXrp.drops(), + env.current()->fees().accountReserve(0, 1).drops() / kDropsPerXrp.drops(), env.current()->fees().increment.drops() / kDropsPerXrp.drops()}; }(); @@ -5927,8 +5928,9 @@ class Vault_test : public beast::unit_test::Suite token->setFieldVL(*field, gMakeZeroBuffer(kEcGamalEncryptedTotalLength)); sb.update(token); + auto const dummyTx = *env.jt(noop(holder)).stx; BEAST_EXPECT( - removeEmptyHolding(sb, holder.id(), MPTIssue(mpt.issuanceID()), j) == + removeEmptyHolding({sb, dummyTx}, holder.id(), MPTIssue(mpt.issuanceID()), j) == tecHAS_OBLIGATIONS); BEAST_EXPECT(sb.peek(tokenKeylet) != nullptr); } diff --git a/src/test/app/XChain_test.cpp b/src/test/app/XChain_test.cpp index 437c329e02..4b007eea13 100644 --- a/src/test/app/XChain_test.cpp +++ b/src/test/app/XChain_test.cpp @@ -141,7 +141,7 @@ struct SEnv XRPAmount reserve(std::uint32_t count) { - return env.current()->fees().accountReserve(count); + return env.current()->fees().accountReserve(count, 1); } XRPAmount @@ -245,9 +245,9 @@ struct Balance T& env; STAmount startAmount; - Balance(T& env, jtx::Account const& account) : account(account), env(env) + Balance(T& env, jtx::Account const& account) + : account(account), env(env), startAmount(env.balance(account)) { - startAmount = env.balance(account); } [[nodiscard]] STAmount @@ -370,7 +370,7 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj XRPAmount reserve(std::uint32_t count) { - return XEnv(*this).env.current()->fees().accountReserve(count); + return XEnv(*this).env.current()->fees().accountReserve(count, 1); } XRPAmount diff --git a/src/test/basics/Buffer_test.cpp b/src/test/basics/Buffer_test.cpp deleted file mode 100644 index c748a3f9dd..0000000000 --- a/src/test/basics/Buffer_test.cpp +++ /dev/null @@ -1,268 +0,0 @@ -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace xrpl::test { - -struct Buffer_test : beast::unit_test::Suite -{ - static bool - sane(Buffer const& b) - { - if (b.empty()) - return b.data() == nullptr; - - return b.data() != nullptr; - } - - void - run() override - { - std::uint8_t const data[] = {0xa8, 0xa1, 0x38, 0x45, 0x23, 0xec, 0xe4, 0x23, - 0x71, 0x6d, 0x2a, 0x18, 0xb4, 0x70, 0xcb, 0xf5, - 0xac, 0x2d, 0x89, 0x4d, 0x19, 0x9c, 0xf0, 0x2c, - 0x15, 0xd1, 0xf9, 0x9b, 0x66, 0xd2, 0x30, 0xd3}; - - Buffer const b0; - BEAST_EXPECT(sane(b0)); - BEAST_EXPECT(b0.empty()); - - Buffer b1{0}; - BEAST_EXPECT(sane(b1)); - BEAST_EXPECT(b1.empty()); - std::memcpy(b1.alloc(16), data, 16); - BEAST_EXPECT(sane(b1)); - BEAST_EXPECT(!b1.empty()); - BEAST_EXPECT(b1.size() == 16); - - Buffer b2{b1.size()}; - BEAST_EXPECT(sane(b2)); - BEAST_EXPECT(!b2.empty()); - BEAST_EXPECT(b2.size() == b1.size()); - std::memcpy(b2.data(), data + 16, 16); - - Buffer b3{data, sizeof(data)}; - BEAST_EXPECT(sane(b3)); - BEAST_EXPECT(!b3.empty()); - BEAST_EXPECT(b3.size() == sizeof(data)); - BEAST_EXPECT(std::memcmp(b3.data(), data, b3.size()) == 0); - - // Check equality and inequality comparisons - BEAST_EXPECT(b0 == b0); - BEAST_EXPECT(b0 != b1); - BEAST_EXPECT(b1 == b1); - BEAST_EXPECT(b1 != b2); - BEAST_EXPECT(b2 != b3); - - // Check copy constructors and copy assignments: - { - testcase("Copy Construction / Assignment"); - - Buffer x{b0}; - BEAST_EXPECT(x == b0); - BEAST_EXPECT(sane(x)); - Buffer y{b1}; - BEAST_EXPECT(y == b1); - BEAST_EXPECT(sane(y)); - x = b2; - BEAST_EXPECT(x == b2); - BEAST_EXPECT(sane(x)); - x = y; - BEAST_EXPECT(x == y); - BEAST_EXPECT(sane(x)); - y = b3; - BEAST_EXPECT(y == b3); - BEAST_EXPECT(sane(y)); - x = b0; - BEAST_EXPECT(x == b0); - BEAST_EXPECT(sane(x)); -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wself-assign-overloaded" -#endif - - x = x; - BEAST_EXPECT(x == b0); - BEAST_EXPECT(sane(x)); - y = y; - BEAST_EXPECT(y == b3); - BEAST_EXPECT(sane(y)); - -#if defined(__clang__) -#pragma clang diagnostic pop -#endif - } - - // Check move constructor & move assignments: - { - testcase("Move Construction / Assignment"); - - static_assert(std::is_nothrow_move_constructible_v); - static_assert(std::is_nothrow_move_assignable_v); - - { // Move-construct from empty buf - Buffer x; - Buffer const y{std::move(x)}; - BEAST_EXPECT(sane(x)); // NOLINT(bugprone-use-after-move) - BEAST_EXPECT(x.empty()); // NOLINT(bugprone-use-after-move) - BEAST_EXPECT(sane(y)); - BEAST_EXPECT(y.empty()); - BEAST_EXPECT(x == y); // NOLINT(bugprone-use-after-move) - } - - { // Move-construct from non-empty buf - Buffer x{b1}; - Buffer const y{std::move(x)}; - BEAST_EXPECT(sane(x)); // NOLINT(bugprone-use-after-move) - BEAST_EXPECT(x.empty()); // NOLINT(bugprone-use-after-move) - BEAST_EXPECT(sane(y)); - BEAST_EXPECT(y == b1); - } - - { // Move assign empty buf to empty buf - Buffer x; - Buffer y; - - x = std::move(y); - BEAST_EXPECT(sane(x)); - BEAST_EXPECT(x.empty()); - BEAST_EXPECT(sane(y)); // NOLINT(bugprone-use-after-move) - BEAST_EXPECT(y.empty()); // NOLINT(bugprone-use-after-move) - } - - { // Move assign non-empty buf to empty buf - Buffer x; - Buffer y{b1}; - - x = std::move(y); - BEAST_EXPECT(sane(x)); - BEAST_EXPECT(x == b1); - BEAST_EXPECT(sane(y)); // NOLINT(bugprone-use-after-move) - BEAST_EXPECT(y.empty()); // NOLINT(bugprone-use-after-move) - } - - { // Move assign empty buf to non-empty buf - Buffer x{b1}; - Buffer y; - - x = std::move(y); - BEAST_EXPECT(sane(x)); - BEAST_EXPECT(x.empty()); - BEAST_EXPECT(sane(y)); // NOLINT(bugprone-use-after-move) - BEAST_EXPECT(y.empty()); // NOLINT(bugprone-use-after-move) - } - - { // Move assign non-empty buf to non-empty buf - Buffer x{b1}; - Buffer y{b2}; - Buffer z{b3}; - - x = std::move(y); - BEAST_EXPECT(sane(x)); - BEAST_EXPECT(!x.empty()); - BEAST_EXPECT(sane(y)); // NOLINT(bugprone-use-after-move) - BEAST_EXPECT(y.empty()); // NOLINT(bugprone-use-after-move) - - x = std::move(z); - BEAST_EXPECT(sane(x)); - BEAST_EXPECT(!x.empty()); - BEAST_EXPECT(sane(z)); // NOLINT(bugprone-use-after-move) - BEAST_EXPECT(z.empty()); // NOLINT(bugprone-use-after-move) - } - } - - { - testcase("Slice Conversion / Construction / Assignment"); - - Buffer w{static_cast(b0)}; - BEAST_EXPECT(sane(w)); - BEAST_EXPECT(w == b0); - - Buffer x{static_cast(b1)}; - BEAST_EXPECT(sane(x)); - BEAST_EXPECT(x == b1); - - Buffer y{static_cast(b2)}; - BEAST_EXPECT(sane(y)); - BEAST_EXPECT(y == b2); - - Buffer z{static_cast(b3)}; - BEAST_EXPECT(sane(z)); - BEAST_EXPECT(z == b3); - - // Assign empty slice to empty buffer - w = static_cast(b0); - BEAST_EXPECT(sane(w)); - BEAST_EXPECT(w == b0); - - // Assign non-empty slice to empty buffer - w = static_cast(b1); - BEAST_EXPECT(sane(w)); - BEAST_EXPECT(w == b1); - - // Assign non-empty slice to non-empty buffer - x = static_cast(b2); - BEAST_EXPECT(sane(x)); - BEAST_EXPECT(x == b2); - - // Assign non-empty slice to non-empty buffer - y = static_cast(z); - BEAST_EXPECT(sane(y)); - BEAST_EXPECT(y == z); - - // Assign empty slice to non-empty buffer: - z = static_cast(b0); - BEAST_EXPECT(sane(z)); - BEAST_EXPECT(z == b0); - } - - { - testcase("Allocation, Deallocation and Clearing"); - - auto test = [this](Buffer const& b, std::size_t i) { - Buffer x{b}; - - // Try to allocate some number of bytes, possibly - // zero (which means clear) and sanity check - x(i); - BEAST_EXPECT(sane(x)); - BEAST_EXPECT(x.size() == i); - BEAST_EXPECT((x.data() == nullptr) == (i == 0)); - - // Try to allocate some more data (always non-zero) - x(i + 1); - BEAST_EXPECT(sane(x)); - BEAST_EXPECT(x.size() == i + 1); - BEAST_EXPECT(x.data() != nullptr); - - // Try to clear: - x.clear(); - BEAST_EXPECT(sane(x)); - BEAST_EXPECT(x.empty()); - BEAST_EXPECT(x.data() == nullptr); - - // Try to clear again: - x.clear(); - BEAST_EXPECT(sane(x)); - BEAST_EXPECT(x.empty()); - BEAST_EXPECT(x.data() == nullptr); - }; - - for (std::size_t i = 0; i < 16; ++i) - { - test(b0, i); - test(b1, i); - } - } - } -}; - -BEAST_DEFINE_TESTSUITE(Buffer, basics, xrpl); - -} // namespace xrpl::test diff --git a/src/test/basics/FileUtilities_test.cpp b/src/test/basics/FileUtilities_test.cpp deleted file mode 100644 index 0e050b5168..0000000000 --- a/src/test/basics/FileUtilities_test.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include - -#include -#include -#include - -#include -#include - -namespace xrpl { - -class FileUtilities_test : public beast::unit_test::Suite -{ -public: - void - testGetFileContents() - { - using namespace xrpl::detail; - using namespace boost::system; - - static constexpr char const* kExpectedContents = - "This file is very short. That's all we need."; - - FileDirGuard const file( - *this, "test_file", "test.txt", "This is temporary text that should get overwritten"); - - error_code ec; - auto const path = file.file(); - - writeFileContents(ec, path, kExpectedContents); - BEAST_EXPECT(!ec); - - { - // Test with no max - auto const good = getFileContents(ec, path); - BEAST_EXPECT(!ec); - BEAST_EXPECT(good == kExpectedContents); - } - - { - // Test with large max - auto const good = getFileContents(ec, path, kilobytes(1)); - BEAST_EXPECT(!ec); - BEAST_EXPECT(good == kExpectedContents); - } - - { - // Test with small max - auto const bad = getFileContents(ec, path, 16); - BEAST_EXPECT(ec && ec.value() == boost::system::errc::file_too_large); - BEAST_EXPECT(bad.empty()); - } - } - - void - run() override - { - testGetFileContents(); - } -}; - -BEAST_DEFINE_TESTSUITE(FileUtilities, basics, xrpl); - -} // namespace xrpl diff --git a/src/test/basics/IOUAmount_test.cpp b/src/test/basics/IOUAmount_test.cpp deleted file mode 100644 index b652d27625..0000000000 --- a/src/test/basics/IOUAmount_test.cpp +++ /dev/null @@ -1,276 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include - -namespace xrpl { - -class IOUAmount_test : public beast::unit_test::Suite -{ -public: - void - testZero() - { - testcase("zero"); - - IOUAmount const z(0, 0); - - BEAST_EXPECT(z.mantissa() == 0); - BEAST_EXPECT(z.exponent() == -100); - BEAST_EXPECT(!z); - BEAST_EXPECT(z.signum() == 0); - BEAST_EXPECT(z == beast::kZero); - - BEAST_EXPECT((z + z) == z); - BEAST_EXPECT((z - z) == z); - BEAST_EXPECT(z == -z); - - IOUAmount const zz(beast::kZero); - BEAST_EXPECT(z == zz); - - // https://github.com/XRPLF/rippled/issues/5170 - IOUAmount const zzz{}; - BEAST_EXPECT(zzz == beast::kZero); - // BEAST_EXPECT(zzz == zz); - } - - void - testSigNum() - { - testcase("signum"); - - IOUAmount const neg(-1, 0); - BEAST_EXPECT(neg.signum() < 0); - - IOUAmount const zer(0, 0); - BEAST_EXPECT(zer.signum() == 0); - - IOUAmount const pos(1, 0); - BEAST_EXPECT(pos.signum() > 0); - } - - void - testBeastZero() - { - testcase("beast::Zero Comparisons"); - - using beast::kZero; - - { - IOUAmount const z(kZero); - BEAST_EXPECT(z == kZero); - BEAST_EXPECT(z >= kZero); - BEAST_EXPECT(z <= kZero); - unexpected(z != kZero); - unexpected(z > kZero); - unexpected(z < kZero); - } - - { - IOUAmount const neg(-2, 0); - BEAST_EXPECT(neg < kZero); - BEAST_EXPECT(neg <= kZero); - BEAST_EXPECT(neg != kZero); - unexpected(neg == kZero); - } - - { - IOUAmount const pos(2, 0); - BEAST_EXPECT(pos > kZero); - BEAST_EXPECT(pos >= kZero); - BEAST_EXPECT(pos != kZero); - unexpected(pos == kZero); - } - } - - void - testComparisons() - { - testcase("IOU Comparisons"); - - IOUAmount const n(-2, 0); - IOUAmount const z(0, 0); - IOUAmount const p(2, 0); - - BEAST_EXPECT(z == z); - BEAST_EXPECT(z >= z); - BEAST_EXPECT(z <= z); - BEAST_EXPECT(z == -z); - // NOLINTBEGIN(misc-redundant-expression) - unexpected(z > z); - unexpected(z < z); - unexpected(z != z); - // NOLINTEND(misc-redundant-expression) - unexpected(z != -z); - - BEAST_EXPECT(n < z); - BEAST_EXPECT(n <= z); - BEAST_EXPECT(n != z); - unexpected(n > z); - unexpected(n >= z); - unexpected(n == z); - - BEAST_EXPECT(p > z); - BEAST_EXPECT(p >= z); - BEAST_EXPECT(p != z); - unexpected(p < z); - unexpected(p <= z); - unexpected(p == z); - - BEAST_EXPECT(n < p); - BEAST_EXPECT(n <= p); - BEAST_EXPECT(n != p); - unexpected(n > p); - unexpected(n >= p); - unexpected(n == p); - - BEAST_EXPECT(p > n); - BEAST_EXPECT(p >= n); - BEAST_EXPECT(p != n); - unexpected(p < n); - unexpected(p <= n); - unexpected(p == n); - - BEAST_EXPECT(p > -p); - BEAST_EXPECT(p >= -p); - BEAST_EXPECT(p != -p); - - BEAST_EXPECT(n < -n); - BEAST_EXPECT(n <= -n); - BEAST_EXPECT(n != -n); - } - - void - testToString() - { - testcase("IOU strings"); - - auto test = [this](IOUAmount const& n, std::string const& expected) { - auto const result = to_string(n); - std::stringstream ss; - ss << "to_string(" << result << "). Expected: " << expected; - BEAST_EXPECTS(result == expected, ss.str()); - }; - - for (auto const mantissaSize : MantissaRange::getAllScales()) - { - NumberMantissaScaleGuard const mg(mantissaSize); - - test(IOUAmount(-2, 0), "-2"); - test(IOUAmount(0, 0), "0"); - test(IOUAmount(2, 0), "2"); - test(IOUAmount(25, -3), "0.025"); - test(IOUAmount(-25, -3), "-0.025"); - test(IOUAmount(25, 1), "250"); - test(IOUAmount(-25, 1), "-250"); - test(IOUAmount(2, 20), "2e20"); - test(IOUAmount(-2, -20), "-2e-20"); - } - } - - void - testMulRatio() - { - testcase("mulRatio"); - - /* The range for the mantissa when normalized */ - static constexpr std::int64_t kMinMantissa = 1000000000000000ull; - static constexpr std::int64_t kMaxMantissa = 9999999999999999ull; - // log(2,maxMantissa) ~ 53.15 - /* The range for the exponent when normalized */ - static constexpr int kMinExponent = -96; - static constexpr int kMaxExponent = 80; - constexpr auto kMaxUInt = std::numeric_limits::max(); - - { - // multiply by a number that would overflow the mantissa, then - // divide by the same number, and check we didn't lose any value - IOUAmount const bigMan(kMaxMantissa, 0); - BEAST_EXPECT(bigMan == mulRatio(bigMan, kMaxUInt, kMaxUInt, true)); - // rounding mode shouldn't matter as the result is exact - BEAST_EXPECT(bigMan == mulRatio(bigMan, kMaxUInt, kMaxUInt, false)); - } - { - // Similar test as above, but for negative values - IOUAmount const bigMan(-kMaxMantissa, 0); - BEAST_EXPECT(bigMan == mulRatio(bigMan, kMaxUInt, kMaxUInt, true)); - // rounding mode shouldn't matter as the result is exact - BEAST_EXPECT(bigMan == mulRatio(bigMan, kMaxUInt, kMaxUInt, false)); - } - - { - // small amounts - IOUAmount const tiny(kMinMantissa, kMinExponent); - // Round up should give the smallest allowable number - BEAST_EXPECT(tiny == mulRatio(tiny, 1, kMaxUInt, true)); - BEAST_EXPECT(tiny == mulRatio(tiny, kMaxUInt - 1, kMaxUInt, true)); - // rounding down should be zero - BEAST_EXPECT(beast::kZero == mulRatio(tiny, 1, kMaxUInt, false)); - BEAST_EXPECT(beast::kZero == mulRatio(tiny, kMaxUInt - 1, kMaxUInt, false)); - - // tiny negative numbers - IOUAmount const tinyNeg(-kMinMantissa, kMinExponent); - // Round up should give zero - BEAST_EXPECT(beast::kZero == mulRatio(tinyNeg, 1, kMaxUInt, true)); - BEAST_EXPECT(beast::kZero == mulRatio(tinyNeg, kMaxUInt - 1, kMaxUInt, true)); - // rounding down should be tiny - BEAST_EXPECT(tinyNeg == mulRatio(tinyNeg, 1, kMaxUInt, false)); - BEAST_EXPECT(tinyNeg == mulRatio(tinyNeg, kMaxUInt - 1, kMaxUInt, false)); - } - - { // rounding - { - IOUAmount const one(1, 0); - auto const rup = mulRatio(one, kMaxUInt - 1, kMaxUInt, true); - auto const rdown = mulRatio(one, kMaxUInt - 1, kMaxUInt, false); - BEAST_EXPECT(rup.mantissa() - rdown.mantissa() == 1); - } - { - IOUAmount const big(kMaxMantissa, kMaxExponent); - auto const rup = mulRatio(big, kMaxUInt - 1, kMaxUInt, true); - auto const rdown = mulRatio(big, kMaxUInt - 1, kMaxUInt, false); - BEAST_EXPECT(rup.mantissa() - rdown.mantissa() == 1); - } - - { - IOUAmount const negOne(-1, 0); - auto const rup = mulRatio(negOne, kMaxUInt - 1, kMaxUInt, true); - auto const rdown = mulRatio(negOne, kMaxUInt - 1, kMaxUInt, false); - BEAST_EXPECT(rup.mantissa() - rdown.mantissa() == 1); - } - } - - { - // division by zero - IOUAmount one(1, 0); - except([&] { mulRatio(one, 1, 0, true); }); - } - - { - // overflow - IOUAmount big(kMaxMantissa, kMaxExponent); - except([&] { mulRatio(big, 2, 0, true); }); - } - } // namespace xrpl - - //-------------------------------------------------------------------------- - - void - run() override - { - testZero(); - testSigNum(); - testBeastZero(); - testComparisons(); - testToString(); - testMulRatio(); - } -}; - -BEAST_DEFINE_TESTSUITE(IOUAmount, basics, xrpl); - -} // namespace xrpl diff --git a/src/test/basics/IntrusiveShared_test.cpp b/src/test/basics/IntrusiveShared_test.cpp deleted file mode 100644 index 185b877f23..0000000000 --- a/src/test/basics/IntrusiveShared_test.cpp +++ /dev/null @@ -1,879 +0,0 @@ - -#include // IWYU pragma: keep -#include // IWYU pragma: keep -#include -#include - -#include -#include -#include -#include -#include // IWYU pragma: keep -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace xrpl::tests { - -/** -Experimentally, we discovered that using std::barrier performs extremely -poorly (~1 hour vs ~1 minute to run the test suite) in certain macOS -environments. To unblock our macOS CI pipeline, we replaced std::barrier with a -custom mutex-based barrier (Barrier) that significantly improves performance -without compromising correctness. For future reference, if we ever consider -reintroducing std::barrier, the following configuration is known to exhibit the -problem: - - Model Name: Mac mini - Model Identifier: Mac14,3 - Model Number: Z16K000R4LL/A - Chip: Apple M2 - Total Number of Cores: 8 (4 performance and 4 efficiency) - Memory: 24 GB - System Firmware Version: 11881.41.5 - OS Loader Version: 11881.1.1 - Apple clang version 16.0.0 (clang-1600.0.26.3) - Target: arm64-apple-darwin24.0.0 - Thread model: posix - - */ -struct Barrier -{ - std::mutex mtx; - std::condition_variable cv; - int count; - int const initial; - - Barrier(int n) : count(n), initial(n) - { - } - - void - arriveAndWait() - { - std::unique_lock lock(mtx); - if (--count == 0) - { - count = initial; - cv.notify_all(); - } - else - { - cv.wait(lock, [&] { return count == initial; }); - } - } -}; - -namespace { -enum class TrackedState : std::uint8_t { - Uninitialized, - Alive, - PartiallyDeletedStarted, - PartiallyDeleted, - DeletedStarted, - Deleted -}; - -class TIBase : public IntrusiveRefCounts -{ -public: - static constexpr std::size_t kMaxStates = 128; - static std::array, kMaxStates> state; - static std::atomic nextId; - static TrackedState - getState(int id) - { - assert(id < state.size()); - return state[id].load(std::memory_order_acquire); - } - static void - resetStates(bool resetCallback) - { - for (int i = 0; i < kMaxStates; ++i) - { - state[i].store(TrackedState::Uninitialized, std::memory_order_release); - } - nextId.store(0, std::memory_order_release); - if (resetCallback) - TIBase::tracingCallback = [](TrackedState, std::optional) {}; - } - - struct ResetStatesGuard - { - bool resetCallback{false}; - - ResetStatesGuard(bool resetCallback) : resetCallback{resetCallback} - { - TIBase::resetStates(resetCallback); - } - ~ResetStatesGuard() - { - TIBase::resetStates(resetCallback); - } - }; - - TIBase() : id{checkoutID()} - { - assert(state.size() > id); - state[id].store(TrackedState::Alive, std::memory_order_relaxed); - } - ~TIBase() override - { - using enum TrackedState; - - assert(state.size() > id); - tracingCallback(state[id].load(std::memory_order_relaxed), DeletedStarted); - - assert(state.size() > id); - // Use relaxed memory order to try to avoid atomic operations from - // adding additional memory synchronizations that may hide threading - // errors in the underlying shared pointer class. - state[id].store(DeletedStarted, std::memory_order_relaxed); - - tracingCallback(DeletedStarted, Deleted); - - assert(state.size() > id); - state[id].store(TrackedState::Deleted, std::memory_order_relaxed); - - tracingCallback(TrackedState::Deleted, std::nullopt); - } - - void - partialDestructor() const - { - using enum TrackedState; - - assert(state.size() > id); - tracingCallback(state[id].load(std::memory_order_relaxed), PartiallyDeletedStarted); - - assert(state.size() > id); - state[id].store(PartiallyDeletedStarted, std::memory_order_relaxed); - - tracingCallback(PartiallyDeletedStarted, PartiallyDeleted); - - assert(state.size() > id); - state[id].store(PartiallyDeleted, std::memory_order_relaxed); - - tracingCallback(PartiallyDeleted, std::nullopt); - } - - static std::function)> tracingCallback; - - int id; - -private: - static int - checkoutID() - { - return nextId.fetch_add(1, std::memory_order_acq_rel); - } -}; - -std::array, TIBase::kMaxStates> TIBase::state; -std::atomic TIBase::nextId{0}; - -std::function)> TIBase::tracingCallback = - [](TrackedState, std::optional) {}; - -} // namespace - -class IntrusiveShared_test : public beast::unit_test::Suite -{ -public: - void - testBasics() - { - testcase("Basics"); - - { - TIBase::ResetStatesGuard const rsg{true}; - - TIBase const b; - BEAST_EXPECT(b.useCount() == 1); - b.addWeakRef(); - BEAST_EXPECT(b.useCount() == 1); - auto s = b.releaseStrongRef(); - BEAST_EXPECT(s == ReleaseStrongRefAction::PartialDestroy); - BEAST_EXPECT(b.useCount() == 0); - TIBase const* pb = &b; - partialDestructorFinished(&pb); - BEAST_EXPECT(!pb); - auto w = b.releaseWeakRef(); - BEAST_EXPECT(w == ReleaseWeakRefAction::Destroy); - } - - std::vector> strong; - std::vector> weak; - { - TIBase::ResetStatesGuard const rsg{true}; - - using enum TrackedState; - auto b = makeSharedIntrusive(); - auto id = b->id; - BEAST_EXPECT(TIBase::getState(id) == Alive); - BEAST_EXPECT(b->useCount() == 1); - for (int i = 0; i < 10; ++i) - { - strong.push_back(b); - } - b.reset(); - BEAST_EXPECT(TIBase::getState(id) == Alive); - strong.resize(strong.size() - 1); - BEAST_EXPECT(TIBase::getState(id) == Alive); - strong.clear(); - BEAST_EXPECT(TIBase::getState(id) == Deleted); - - b = makeSharedIntrusive(); - id = b->id; - BEAST_EXPECT(TIBase::getState(id) == Alive); - BEAST_EXPECT(b->useCount() == 1); - for (int i = 0; i < 10; ++i) - { - weak.emplace_back(b); - BEAST_EXPECT(b->useCount() == 1); - } - BEAST_EXPECT(TIBase::getState(id) == Alive); - weak.resize(weak.size() - 1); - BEAST_EXPECT(TIBase::getState(id) == Alive); - b.reset(); - BEAST_EXPECT(TIBase::getState(id) == PartiallyDeleted); - while (!weak.empty()) - { - weak.resize(weak.size() - 1); - if (!weak.empty()) - BEAST_EXPECT(TIBase::getState(id) == PartiallyDeleted); - } - BEAST_EXPECT(TIBase::getState(id) == Deleted); - } - { - TIBase::ResetStatesGuard const rsg{true}; - - using enum TrackedState; - auto b = makeSharedIntrusive(); - auto id = b->id; - BEAST_EXPECT(TIBase::getState(id) == Alive); - WeakIntrusive w{b}; - BEAST_EXPECT(TIBase::getState(id) == Alive); - auto s = w.lock(); - BEAST_EXPECT(s && s->useCount() == 2); - b.reset(); - BEAST_EXPECT(TIBase::getState(id) == Alive); - BEAST_EXPECT(s && s->useCount() == 1); - s.reset(); - BEAST_EXPECT(TIBase::getState(id) == PartiallyDeleted); - BEAST_EXPECT(w.expired()); - s = w.lock(); - // Cannot convert a weak pointer to a strong pointer if object is - // already partially deleted - BEAST_EXPECT(!s); - w.reset(); - BEAST_EXPECT(TIBase::getState(id) == Deleted); - } - { - TIBase::ResetStatesGuard const rsg{true}; - - using enum TrackedState; - using swu = SharedWeakUnion; - swu b = makeSharedIntrusive(); - BEAST_EXPECT(b.isStrong() && b.useCount() == 1); - auto id = b.get()->id; - BEAST_EXPECT(TIBase::getState(id) == Alive); - swu w = b; - BEAST_EXPECT(TIBase::getState(id) == Alive); - BEAST_EXPECT(w.isStrong() && b.useCount() == 2); - w.convertToWeak(); - BEAST_EXPECT(w.isWeak() && b.useCount() == 1); - swu s = w; - BEAST_EXPECT(s.isWeak() && b.useCount() == 1); - s.convertToStrong(); - BEAST_EXPECT(s.isStrong() && b.useCount() == 2); - b.reset(); - BEAST_EXPECT(TIBase::getState(id) == Alive); - BEAST_EXPECT(s.useCount() == 1); - BEAST_EXPECT(!w.expired()); - s.reset(); - BEAST_EXPECT(TIBase::getState(id) == PartiallyDeleted); - BEAST_EXPECT(w.expired()); - w.convertToStrong(); - // Cannot convert a weak pointer to a strong pointer if object is - // already partially deleted - BEAST_EXPECT(w.isWeak()); - w.reset(); - BEAST_EXPECT(TIBase::getState(id) == Deleted); - } - { - // Testing SharedWeakUnion assignment operator - - TIBase::ResetStatesGuard const rsg{true}; - - auto strong1 = makeSharedIntrusive(); - auto strong2 = makeSharedIntrusive(); - - auto id1 = strong1->id; - auto id2 = strong2->id; - - BEAST_EXPECT(id1 != id2); - - SharedWeakUnion union1 = strong1; - SharedWeakUnion union2 = strong2; - - BEAST_EXPECT(union1.isStrong()); - BEAST_EXPECT(union2.isStrong()); - BEAST_EXPECT(union1.get() == strong1.get()); - BEAST_EXPECT(union2.get() == strong2.get()); - - // 1) Normal assignment: explicitly calls SharedWeakUnion assignment - union1 = union2; - BEAST_EXPECT(union1.isStrong()); - BEAST_EXPECT(union2.isStrong()); - BEAST_EXPECT(union1.get() == union2.get()); - BEAST_EXPECT(TIBase::getState(id1) == TrackedState::Alive); - BEAST_EXPECT(TIBase::getState(id2) == TrackedState::Alive); - - // 2) Test self-assignment - BEAST_EXPECT(union1.isStrong()); - BEAST_EXPECT(TIBase::getState(id1) == TrackedState::Alive); - int const initialRefCount = strong1->useCount(); -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wself-assign-overloaded" - union1 = union1; // Self-assignment -#pragma clang diagnostic pop - BEAST_EXPECT(union1.isStrong()); - BEAST_EXPECT(TIBase::getState(id1) == TrackedState::Alive); - BEAST_EXPECT(strong1->useCount() == initialRefCount); - - // 3) Test assignment from null union pointer - union1 = SharedWeakUnion(); - BEAST_EXPECT(union1.get() == nullptr); - - // 4) Test assignment to expired union pointer - strong2.reset(); - union2.reset(); - union1 = union2; - BEAST_EXPECT(union1.get() == nullptr); - BEAST_EXPECT(TIBase::getState(id2) == TrackedState::Deleted); - } - } - - void - testPartialDelete() - { - testcase("Partial Delete"); - - // This test creates two threads. One with a strong pointer and one - // with a weak pointer. The strong pointer is reset while the weak - // pointer still holds a reference, triggering a partial delete. - // While the partial delete function runs (a sleep is inserted) the - // weak pointer is reset. The destructor should wait to run until - // after the partial delete function has completed running. - - using enum TrackedState; - - TIBase::ResetStatesGuard const rsg{true}; - - auto strong = makeSharedIntrusive(); - WeakIntrusive weak{strong}; - bool destructorRan = false; - bool partialDeleteRan = false; - std::latch partialDeleteStartedSyncPoint{2}; - strong->tracingCallback = [&](TrackedState cur, std::optional next) { - using enum TrackedState; - if (next == DeletedStarted) - { - // strong goes out of scope while weak is still in scope - // This checks that partialDelete has run to completion - // before the destructor is called. A sleep is inserted - // inside the partial delete to make sure the destructor is - // given an opportunity to run during partial delete. - BEAST_EXPECT(cur == PartiallyDeleted); - } - if (next == PartiallyDeletedStarted) - { - partialDeleteStartedSyncPoint.arrive_and_wait(); - using namespace std::chrono_literals; - // Sleep and let the weak pointer go out of scope, - // potentially triggering a destructor while partial delete - // is running. The test is to make sure that doesn't happen. - std::this_thread::sleep_for(800ms); - } - if (next == PartiallyDeleted) - { - BEAST_EXPECT(!partialDeleteRan && !destructorRan); - partialDeleteRan = true; - } - if (next == Deleted) - { - BEAST_EXPECT(!destructorRan); - destructorRan = true; - } - }; - std::thread t1{[&] { - partialDeleteStartedSyncPoint.arrive_and_wait(); - weak.reset(); // Trigger a full delete as soon as the partial - // delete starts - }}; - std::thread t2{[&] { - strong.reset(); // Trigger a partial delete - }}; - t1.join(); - t2.join(); - - BEAST_EXPECT(destructorRan && partialDeleteRan); - } - - void - testDestructor() - { - testcase("Destructor"); - - // This test creates two threads. One with a strong pointer and one - // with a weak pointer. The weak pointer is reset while the strong - // pointer still holds a reference. Then the strong pointer is - // reset. Only the destructor should run. The partial destructor - // should not be called. Since the weak reset runs to completion - // before the strong pointer is reset, threading doesn't add much to - // this test, but there is no harm in keeping it. - - using enum TrackedState; - - TIBase::ResetStatesGuard const rsg{true}; - - auto strong = makeSharedIntrusive(); - WeakIntrusive weak{strong}; - bool destructorRan = false; - bool partialDeleteRan = false; - std::latch weakResetSyncPoint{2}; - strong->tracingCallback = [&](TrackedState cur, std::optional next) { - using enum TrackedState; - if (next == PartiallyDeleted) - { - BEAST_EXPECT(!partialDeleteRan && !destructorRan); - partialDeleteRan = true; - } - if (next == Deleted) - { - BEAST_EXPECT(!destructorRan); - destructorRan = true; - } - }; - std::thread t1{[&] { - weak.reset(); - weakResetSyncPoint.arrive_and_wait(); - }}; - std::thread t2{[&] { - weakResetSyncPoint.arrive_and_wait(); - strong.reset(); // Trigger a partial delete - }}; - t1.join(); - t2.join(); - - BEAST_EXPECT(destructorRan && !partialDeleteRan); - } - - void - testMultithreadedClearMixedVariant() - { - testcase("Multithreaded Clear Mixed Variant"); - - // This test creates and destroys many strong and weak pointers in a - // loop. There is a random mix of strong and weak pointers stored in - // a vector (held as a variant). Both threads clear all the pointers - // and check that the invariants hold. - - using enum TrackedState; - TIBase::ResetStatesGuard const rsg{true}; - - std::atomic destructionState{0}; - // returns destructorRan and partialDestructorRan (in that order) - auto getDestructorState = [&]() -> std::pair { - int const s = destructionState.load(std::memory_order_relaxed); - return {(s & 1) != 0, (s & 2) != 0}; - }; - auto setDestructorRan = [&]() -> void { - destructionState.fetch_or(1, std::memory_order_acq_rel); - }; - auto setPartialDeleteRan = [&]() -> void { - destructionState.fetch_or(2, std::memory_order_acq_rel); - }; - auto tracingCallback = [&](TrackedState cur, std::optional next) { - using enum TrackedState; - auto [destructorRan, partialDeleteRan] = getDestructorState(); - if (next == PartiallyDeleted) - { - BEAST_EXPECT(!partialDeleteRan && !destructorRan); - setPartialDeleteRan(); - } - if (next == Deleted) - { - BEAST_EXPECT(!destructorRan); - setDestructorRan(); - } - }; - auto createVecOfPointers = [&](auto const& toClone, std::default_random_engine& eng) - -> std::vector, WeakIntrusive>> { - std::vector, WeakIntrusive>> result; - std::uniform_int_distribution<> toCreateDist(4, 64); - std::uniform_int_distribution<> isStrongDist(0, 1); - auto numToCreate = toCreateDist(eng); - result.reserve(numToCreate); - for (int i = 0; i < numToCreate; ++i) - { - if (isStrongDist(eng)) - { - result.emplace_back(SharedIntrusive(toClone)); - } - else - { - result.emplace_back(WeakIntrusive(toClone)); - } - } - return result; - }; - static constexpr int kLoopIters = 2 * 1024; - static constexpr int kNumThreads = 16; - std::vector> toClone; - Barrier loopStartSyncPoint{kNumThreads}; - Barrier postCreateToCloneSyncPoint{kNumThreads}; - Barrier postCreateVecOfPointersSyncPoint{kNumThreads}; - auto engines = [&]() -> std::vector { - std::random_device rd; - std::vector result; - result.reserve(kNumThreads); - for (int i = 0; i < kNumThreads; ++i) - result.emplace_back(rd()); - return result; - }(); - - // cloneAndDestroy clones the strong pointer into a vector of mixed - // strong and weak pointers and destroys them all at once. - // threadId==0 is special. - auto cloneAndDestroy = [&](int threadId) { - for (int i = 0; i < kLoopIters; ++i) - { - // ------ Sync Point ------ - loopStartSyncPoint.arriveAndWait(); - - // only thread 0 should reset the state - std::optional rsg; - if (threadId == 0) - { - // Thread 0 is the genesis thread. It creates the strong - // pointers to be cloned by the other threads. This - // thread will also check that the destructor ran and - // clear the temporary variables. - - rsg.emplace(false); - auto [destructorRan, partialDeleteRan] = getDestructorState(); - BEAST_EXPECT(!i || destructorRan); - destructionState.store(0, std::memory_order_release); - - toClone.clear(); - toClone.resize(kNumThreads); - auto strong = makeSharedIntrusive(); - strong->tracingCallback = tracingCallback; - std::ranges::fill(toClone, strong); - } - - // ------ Sync Point ------ - postCreateToCloneSyncPoint.arriveAndWait(); - - auto v = createVecOfPointers(toClone[threadId], engines[threadId]); - toClone[threadId].reset(); - - // ------ Sync Point ------ - postCreateVecOfPointersSyncPoint.arriveAndWait(); - - v.clear(); - } - }; - std::vector threads; - threads.reserve(kNumThreads); - for (int i = 0; i < kNumThreads; ++i) - { - threads.emplace_back(cloneAndDestroy, i); - } - for (int i = 0; i < kNumThreads; ++i) - { - threads[i].join(); - } - } - - void - testMultithreadedClearMixedUnion() - { - testcase("Multithreaded Clear Mixed Union"); - - // This test creates and destroys many SharedWeak pointers in a - // loop. All the pointers start as strong and a loop randomly - // convert them between strong and weak pointers. Both threads clear - // all the pointers and check that the invariants hold. - // - // Note: This test also differs from the test above in that the pointers - // randomly change from strong to weak and from weak to strong in a - // loop. This can't be done in the variant test above because variant is - // not thread safe while the SharedWeakUnion is thread safe. - - using enum TrackedState; - - TIBase::ResetStatesGuard const rsg{true}; - - std::atomic destructionState{0}; - // returns destructorRan and partialDestructorRan (in that order) - auto getDestructorState = [&]() -> std::pair { - int const s = destructionState.load(std::memory_order_relaxed); - return {(s & 1) != 0, (s & 2) != 0}; - }; - auto setDestructorRan = [&]() -> void { - destructionState.fetch_or(1, std::memory_order_acq_rel); - }; - auto setPartialDeleteRan = [&]() -> void { - destructionState.fetch_or(2, std::memory_order_acq_rel); - }; - auto tracingCallback = [&](TrackedState cur, std::optional next) { - using enum TrackedState; - auto [destructorRan, partialDeleteRan] = getDestructorState(); - if (next == PartiallyDeleted) - { - BEAST_EXPECT(!partialDeleteRan && !destructorRan); - setPartialDeleteRan(); - } - if (next == Deleted) - { - BEAST_EXPECT(!destructorRan); - setDestructorRan(); - } - }; - auto createVecOfPointers = - [&](auto const& toClone, - std::default_random_engine& eng) -> std::vector> { - std::vector> result; - std::uniform_int_distribution<> toCreateDist(4, 64); - auto numToCreate = toCreateDist(eng); - result.reserve(numToCreate); - for (int i = 0; i < numToCreate; ++i) - result.emplace_back(SharedIntrusive(toClone)); - return result; - }; - static constexpr int kLoopIters = 2 * 1024; - static constexpr int kFlipPointersLoopIters = 256; - static constexpr int kNumThreads = 16; - std::vector> toClone; - Barrier loopStartSyncPoint{kNumThreads}; - Barrier postCreateToCloneSyncPoint{kNumThreads}; - Barrier postCreateVecOfPointersSyncPoint{kNumThreads}; - Barrier postFlipPointersLoopSyncPoint{kNumThreads}; - auto engines = [&]() -> std::vector { - std::random_device rd; - std::vector result; - result.reserve(kNumThreads); - for (int i = 0; i < kNumThreads; ++i) - result.emplace_back(rd()); - return result; - }(); - - // cloneAndDestroy clones the strong pointer into a vector of - // mixed strong and weak pointers, runs a loop that randomly - // changes strong pointers to weak pointers, and destroys them - // all at once. - auto cloneAndDestroy = [&](int threadId) { - for (int i = 0; i < kLoopIters; ++i) - { - // ------ Sync Point ------ - loopStartSyncPoint.arriveAndWait(); - - // only thread 0 should reset the state - std::optional rsg; - if (threadId == 0) - { - // threadId 0 is the genesis thread. It creates the - // strong point to be cloned by the other threads. This - // thread will also check that the destructor ran and - // clear the temporary variables. - rsg.emplace(false); - auto [destructorRan, partialDeleteRan] = getDestructorState(); - BEAST_EXPECT(!i || destructorRan); - destructionState.store(0, std::memory_order_release); - - toClone.clear(); - toClone.resize(kNumThreads); - auto strong = makeSharedIntrusive(); - strong->tracingCallback = tracingCallback; - std::ranges::fill(toClone, strong); - } - - // ------ Sync Point ------ - postCreateToCloneSyncPoint.arriveAndWait(); - - auto v = createVecOfPointers(toClone[threadId], engines[threadId]); - toClone[threadId].reset(); - - // ------ Sync Point ------ - postCreateVecOfPointersSyncPoint.arriveAndWait(); - - std::uniform_int_distribution<> isStrongDist(0, 1); - for (int f = 0; f < kFlipPointersLoopIters; ++f) - { - for (auto& p : v) - { - if (isStrongDist(engines[threadId])) - { - p.convertToStrong(); - } - else - { - p.convertToWeak(); - } - } - } - - // ------ Sync Point ------ - postFlipPointersLoopSyncPoint.arriveAndWait(); - - v.clear(); - } - }; - std::vector threads; - threads.reserve(kNumThreads); - for (int i = 0; i < kNumThreads; ++i) - { - threads.emplace_back(cloneAndDestroy, i); - } - for (int i = 0; i < kNumThreads; ++i) - { - threads[i].join(); - } - } - - void - testMultithreadedLockingWeak() - { - testcase("Multithreaded Locking Weak"); - - // This test creates a single shared atomic pointer that multiple thread - // create weak pointers from. The threads then lock the weak pointers. - // Both threads clear all the pointers and check that the invariants - // hold. - - using enum TrackedState; - - TIBase::ResetStatesGuard const rsg{true}; - - std::atomic destructionState{0}; - // returns destructorRan and partialDestructorRan (in that order) - auto getDestructorState = [&]() -> std::pair { - int const s = destructionState.load(std::memory_order_relaxed); - return {(s & 1) != 0, (s & 2) != 0}; - }; - auto setDestructorRan = [&]() -> void { - destructionState.fetch_or(1, std::memory_order_acq_rel); - }; - auto setPartialDeleteRan = [&]() -> void { - destructionState.fetch_or(2, std::memory_order_acq_rel); - }; - auto tracingCallback = [&](TrackedState cur, std::optional next) { - using enum TrackedState; - auto [destructorRan, partialDeleteRan] = getDestructorState(); - if (next == PartiallyDeleted) - { - BEAST_EXPECT(!partialDeleteRan && !destructorRan); - setPartialDeleteRan(); - } - if (next == Deleted) - { - BEAST_EXPECT(!destructorRan); - setDestructorRan(); - } - }; - - static constexpr int kLoopIters = 2 * 1024; - static constexpr int kLockWeakLoopIters = 256; - static constexpr int kNumThreads = 16; - std::vector> toLock; - Barrier loopStartSyncPoint{kNumThreads}; - Barrier postCreateToLockSyncPoint{kNumThreads}; - Barrier postLockWeakLoopSyncPoint{kNumThreads}; - - // lockAndDestroy creates weak pointers from the strong pointer - // and runs a loop that locks the weak pointer. At the end of the loop - // all the pointers are destroyed all at once. - auto lockAndDestroy = [&](int threadId) { - for (int i = 0; i < kLoopIters; ++i) - { - // ------ Sync Point ------ - loopStartSyncPoint.arriveAndWait(); - - // only thread 0 should reset the state - std::optional rsg; - if (threadId == 0) - { - // threadId 0 is the genesis thread. It creates the - // strong point to be locked by the other threads. This - // thread will also check that the destructor ran and - // clear the temporary variables. - rsg.emplace(false); - auto [destructorRan, partialDeleteRan] = getDestructorState(); - BEAST_EXPECT(!i || destructorRan); - destructionState.store(0, std::memory_order_release); - - toLock.clear(); - toLock.resize(kNumThreads); - auto strong = makeSharedIntrusive(); - strong->tracingCallback = tracingCallback; - std::ranges::fill(toLock, strong); - } - - // ------ Sync Point ------ - postCreateToLockSyncPoint.arriveAndWait(); - - // Multiple threads all create a weak pointer from the same - // strong pointer - WeakIntrusive const weak{toLock[threadId]}; - for (int wi = 0; wi < kLockWeakLoopIters; ++wi) - { - BEAST_EXPECT(!weak.expired()); - auto strong = weak.lock(); - BEAST_EXPECT(strong); - } - - // ------ Sync Point ------ - postLockWeakLoopSyncPoint.arriveAndWait(); - - toLock[threadId].reset(); - } - }; - std::vector threads; - threads.reserve(kNumThreads); - for (int i = 0; i < kNumThreads; ++i) - { - threads.emplace_back(lockAndDestroy, i); - } - for (int i = 0; i < kNumThreads; ++i) - { - threads[i].join(); - } - } - - void - run() override - { - testBasics(); - testPartialDelete(); - testDestructor(); - testMultithreadedClearMixedVariant(); - testMultithreadedClearMixedUnion(); - testMultithreadedLockingWeak(); - } -}; // namespace tests - -BEAST_DEFINE_TESTSUITE(IntrusiveShared, basics, xrpl); -} // namespace xrpl::tests diff --git a/src/test/basics/KeyCache_test.cpp b/src/test/basics/KeyCache_test.cpp deleted file mode 100644 index e83ae4c277..0000000000 --- a/src/test/basics/KeyCache_test.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include - -#include -#include // IWYU pragma: keep -#include -#include -#include - -#include - -namespace xrpl { - -class KeyCache_test : public beast::unit_test::Suite -{ -public: - void - run() override - { - using namespace std::chrono_literals; - TestStopwatch clock; - clock.set(0); - - using Key = std::string; - using Cache = TaggedCache; - - test::SuiteJournal j("KeyCacheTest", *this); - - // Insert an item, retrieve it, and age it so it gets purged. - { - Cache c("test", LedgerIndex(1), 2s, clock, j); - - BEAST_EXPECT(c.size() == 0); - BEAST_EXPECT(c.insert("one")); - BEAST_EXPECT(!c.insert("one")); - BEAST_EXPECT(c.size() == 1); - BEAST_EXPECT(c.touchIfExists("one")); - ++clock; - c.sweep(); - BEAST_EXPECT(c.size() == 1); - ++clock; - c.sweep(); - BEAST_EXPECT(c.size() == 0); - BEAST_EXPECT(!c.touchIfExists("one")); - } - - // Insert two items, have one expire - { - Cache c("test", LedgerIndex(2), 2s, clock, j); - - BEAST_EXPECT(c.insert("one")); - BEAST_EXPECT(c.size() == 1); - BEAST_EXPECT(c.insert("two")); - BEAST_EXPECT(c.size() == 2); - ++clock; - c.sweep(); - BEAST_EXPECT(c.size() == 2); - BEAST_EXPECT(c.touchIfExists("two")); - ++clock; - c.sweep(); - BEAST_EXPECT(c.size() == 1); - } - - // Insert three items (1 over limit), sweep - { - Cache c("test", LedgerIndex(2), 3s, clock, j); - - BEAST_EXPECT(c.insert("one")); - ++clock; - BEAST_EXPECT(c.insert("two")); - ++clock; - BEAST_EXPECT(c.insert("three")); - ++clock; - BEAST_EXPECT(c.size() == 3); - c.sweep(); - BEAST_EXPECT(c.size() < 3); - } - } -}; - -BEAST_DEFINE_TESTSUITE(KeyCache, basics, xrpl); - -} // namespace xrpl diff --git a/src/test/basics/StringUtilities_test.cpp b/src/test/basics/StringUtilities_test.cpp deleted file mode 100644 index e12bc53857..0000000000 --- a/src/test/basics/StringUtilities_test.cpp +++ /dev/null @@ -1,309 +0,0 @@ -#include -#include -#include -#include - -#include - -namespace xrpl { - -class StringUtilities_test : public beast::unit_test::Suite -{ -public: - void - testUnHexSuccess(std::string const& strIn, std::string const& strExpected) - { - auto rv = strUnHex(strIn); - BEAST_EXPECT(rv); - - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - BEAST_EXPECT(makeSlice(*rv) == makeSlice(strExpected)); - } - - void - testUnHexFailure(std::string const& strIn) - { - auto rv = strUnHex(strIn); - BEAST_EXPECT(!rv); - } - - void - testUnHex() - { - testcase("strUnHex"); - - testUnHexSuccess("526970706c6544", "RippleD"); - testUnHexSuccess("A", "\n"); - testUnHexSuccess("0A", "\n"); - testUnHexSuccess("D0A", "\r\n"); - testUnHexSuccess("0D0A", "\r\n"); - testUnHexSuccess("200D0A", " \r\n"); - testUnHexSuccess("282A2B2C2D2E2F29", "(*+,-./)"); - - // Check for things which contain some or only invalid characters - testUnHexFailure("123X"); - testUnHexFailure("V"); - testUnHexFailure("XRP"); - } - - void - testParseUrl() - { - testcase("parseUrl"); - - // Expected passes. - { - ParsedUrl pUrl; - BEAST_EXPECT(parseUrl(pUrl, "scheme://")); - BEAST_EXPECT(pUrl.scheme == "scheme"); - BEAST_EXPECT(pUrl.username.empty()); - BEAST_EXPECT(pUrl.password.empty()); - BEAST_EXPECT(pUrl.domain.empty()); - BEAST_EXPECT(!pUrl.port); - // RFC 3986: - // > In general, a URI that uses the generic syntax for authority - // with an empty path should be normalized to a path of "/". - // Do we want to normalize paths? - BEAST_EXPECT(pUrl.path.empty()); - } - - { - ParsedUrl pUrl; - BEAST_EXPECT(parseUrl(pUrl, "scheme:///")); - BEAST_EXPECT(pUrl.scheme == "scheme"); - BEAST_EXPECT(pUrl.username.empty()); - BEAST_EXPECT(pUrl.password.empty()); - BEAST_EXPECT(pUrl.domain.empty()); - BEAST_EXPECT(!pUrl.port); - BEAST_EXPECT(pUrl.path == "/"); - } - - { - ParsedUrl pUrl; - BEAST_EXPECT(parseUrl(pUrl, "lower://domain")); - BEAST_EXPECT(pUrl.scheme == "lower"); - BEAST_EXPECT(pUrl.username.empty()); - BEAST_EXPECT(pUrl.password.empty()); - BEAST_EXPECT(pUrl.domain == "domain"); - BEAST_EXPECT(!pUrl.port); - BEAST_EXPECT(pUrl.path.empty()); - } - - { - ParsedUrl pUrl; - BEAST_EXPECT(parseUrl(pUrl, "UPPER://domain:234/")); - BEAST_EXPECT(pUrl.scheme == "upper"); - BEAST_EXPECT(pUrl.username.empty()); - BEAST_EXPECT(pUrl.password.empty()); - BEAST_EXPECT(pUrl.domain == "domain"); - BEAST_EXPECT(*pUrl.port == 234); // NOLINT(bugprone-unchecked-optional-access) - BEAST_EXPECT(pUrl.path == "/"); - } - - { - ParsedUrl pUrl; - BEAST_EXPECT(parseUrl(pUrl, "Mixed://domain/path")); - BEAST_EXPECT(pUrl.scheme == "mixed"); - BEAST_EXPECT(pUrl.username.empty()); - BEAST_EXPECT(pUrl.password.empty()); - BEAST_EXPECT(pUrl.domain == "domain"); - BEAST_EXPECT(!pUrl.port); - BEAST_EXPECT(pUrl.path == "/path"); - } - - { - ParsedUrl pUrl; - BEAST_EXPECT(parseUrl(pUrl, "scheme://[::1]:123/path")); - BEAST_EXPECT(pUrl.scheme == "scheme"); - BEAST_EXPECT(pUrl.username.empty()); - BEAST_EXPECT(pUrl.password.empty()); - BEAST_EXPECT(pUrl.domain == "::1"); - BEAST_EXPECT(*pUrl.port == 123); // NOLINT(bugprone-unchecked-optional-access) - BEAST_EXPECT(pUrl.path == "/path"); - } - - { - ParsedUrl pUrl; - BEAST_EXPECT(parseUrl(pUrl, "scheme://user:pass@domain:123/abc:321")); - BEAST_EXPECT(pUrl.scheme == "scheme"); - BEAST_EXPECT(pUrl.username == "user"); - BEAST_EXPECT(pUrl.password == "pass"); - BEAST_EXPECT(pUrl.domain == "domain"); - BEAST_EXPECT(*pUrl.port == 123); // NOLINT(bugprone-unchecked-optional-access) - BEAST_EXPECT(pUrl.path == "/abc:321"); - } - - { - ParsedUrl pUrl; - BEAST_EXPECT(parseUrl(pUrl, "scheme://user@domain:123/abc:321")); - BEAST_EXPECT(pUrl.scheme == "scheme"); - BEAST_EXPECT(pUrl.username == "user"); - BEAST_EXPECT(pUrl.password.empty()); - BEAST_EXPECT(pUrl.domain == "domain"); - BEAST_EXPECT(*pUrl.port == 123); // NOLINT(bugprone-unchecked-optional-access) - BEAST_EXPECT(pUrl.path == "/abc:321"); - } - - { - ParsedUrl pUrl; - BEAST_EXPECT(parseUrl(pUrl, "scheme://:pass@domain:123/abc:321")); - BEAST_EXPECT(pUrl.scheme == "scheme"); - BEAST_EXPECT(pUrl.username.empty()); - BEAST_EXPECT(pUrl.password == "pass"); - BEAST_EXPECT(pUrl.domain == "domain"); - BEAST_EXPECT(*pUrl.port == 123); // NOLINT(bugprone-unchecked-optional-access) - BEAST_EXPECT(pUrl.path == "/abc:321"); - } - - { - ParsedUrl pUrl; - BEAST_EXPECT(parseUrl(pUrl, "scheme://domain:123/abc:321")); - BEAST_EXPECT(pUrl.scheme == "scheme"); - BEAST_EXPECT(pUrl.username.empty()); - BEAST_EXPECT(pUrl.password.empty()); - BEAST_EXPECT(pUrl.domain == "domain"); - BEAST_EXPECT(*pUrl.port == 123); // NOLINT(bugprone-unchecked-optional-access) - BEAST_EXPECT(pUrl.path == "/abc:321"); - } - - { - ParsedUrl pUrl; - BEAST_EXPECT(parseUrl(pUrl, "scheme://user:pass@domain/abc:321")); - BEAST_EXPECT(pUrl.scheme == "scheme"); - BEAST_EXPECT(pUrl.username == "user"); - BEAST_EXPECT(pUrl.password == "pass"); - BEAST_EXPECT(pUrl.domain == "domain"); - BEAST_EXPECT(!pUrl.port); - BEAST_EXPECT(pUrl.path == "/abc:321"); - } - - { - ParsedUrl pUrl; - BEAST_EXPECT(parseUrl(pUrl, "scheme://user@domain/abc:321")); - BEAST_EXPECT(pUrl.scheme == "scheme"); - BEAST_EXPECT(pUrl.username == "user"); - BEAST_EXPECT(pUrl.password.empty()); - BEAST_EXPECT(pUrl.domain == "domain"); - BEAST_EXPECT(!pUrl.port); - BEAST_EXPECT(pUrl.path == "/abc:321"); - } - - { - ParsedUrl pUrl; - BEAST_EXPECT(parseUrl(pUrl, "scheme://:pass@domain/abc:321")); - BEAST_EXPECT(pUrl.scheme == "scheme"); - BEAST_EXPECT(pUrl.username.empty()); - BEAST_EXPECT(pUrl.password == "pass"); - BEAST_EXPECT(pUrl.domain == "domain"); - BEAST_EXPECT(!pUrl.port); - BEAST_EXPECT(pUrl.path == "/abc:321"); - } - - { - ParsedUrl pUrl; - BEAST_EXPECT(parseUrl(pUrl, "scheme://domain/abc:321")); - BEAST_EXPECT(pUrl.scheme == "scheme"); - BEAST_EXPECT(pUrl.username.empty()); - BEAST_EXPECT(pUrl.password.empty()); - BEAST_EXPECT(pUrl.domain == "domain"); - BEAST_EXPECT(!pUrl.port); - BEAST_EXPECT(pUrl.path == "/abc:321"); - } - - { - ParsedUrl pUrl; - BEAST_EXPECT(parseUrl(pUrl, "scheme:///path/to/file")); - BEAST_EXPECT(pUrl.scheme == "scheme"); - BEAST_EXPECT(pUrl.username.empty()); - BEAST_EXPECT(pUrl.password.empty()); - BEAST_EXPECT(pUrl.domain.empty()); - BEAST_EXPECT(!pUrl.port); - BEAST_EXPECT(pUrl.path == "/path/to/file"); - } - - { - ParsedUrl pUrl; - BEAST_EXPECT(parseUrl(pUrl, "scheme://user:pass@domain/path/with/an@sign")); - BEAST_EXPECT(pUrl.scheme == "scheme"); - BEAST_EXPECT(pUrl.username == "user"); - BEAST_EXPECT(pUrl.password == "pass"); - BEAST_EXPECT(pUrl.domain == "domain"); - BEAST_EXPECT(!pUrl.port); - BEAST_EXPECT(pUrl.path == "/path/with/an@sign"); - } - - { - ParsedUrl pUrl; - BEAST_EXPECT(parseUrl(pUrl, "scheme://domain/path/with/an@sign")); - BEAST_EXPECT(pUrl.scheme == "scheme"); - BEAST_EXPECT(pUrl.username.empty()); - BEAST_EXPECT(pUrl.password.empty()); - BEAST_EXPECT(pUrl.domain == "domain"); - BEAST_EXPECT(!pUrl.port); - BEAST_EXPECT(pUrl.path == "/path/with/an@sign"); - } - - { - ParsedUrl pUrl; - BEAST_EXPECT(parseUrl(pUrl, "scheme://:999/")); - BEAST_EXPECT(pUrl.scheme == "scheme"); - BEAST_EXPECT(pUrl.username.empty()); - BEAST_EXPECT(pUrl.password.empty()); - BEAST_EXPECT(pUrl.domain == ":999"); - BEAST_EXPECT(!pUrl.port); - BEAST_EXPECT(pUrl.path == "/"); - } - - { - ParsedUrl pUrl; - BEAST_EXPECT(parseUrl(pUrl, "http://::1:1234/validators")); - BEAST_EXPECT(pUrl.scheme == "http"); - BEAST_EXPECT(pUrl.username.empty()); - BEAST_EXPECT(pUrl.password.empty()); - BEAST_EXPECT(pUrl.domain == "::0.1.18.52"); - BEAST_EXPECT(!pUrl.port); - BEAST_EXPECT(pUrl.path == "/validators"); - } - - // Expected fails. - { - ParsedUrl pUrl; - BEAST_EXPECT(!parseUrl(pUrl, "")); - BEAST_EXPECT(!parseUrl(pUrl, "nonsense")); - BEAST_EXPECT(!parseUrl(pUrl, "://")); - BEAST_EXPECT(!parseUrl(pUrl, ":///")); - BEAST_EXPECT(!parseUrl(pUrl, "scheme://user:pass@domain:65536/abc:321")); - BEAST_EXPECT(!parseUrl(pUrl, "UPPER://domain:23498765/")); - BEAST_EXPECT(!parseUrl(pUrl, "UPPER://domain:0/")); - BEAST_EXPECT(!parseUrl(pUrl, "UPPER://domain:+7/")); - BEAST_EXPECT(!parseUrl(pUrl, "UPPER://domain:-7234/")); - BEAST_EXPECT(!parseUrl(pUrl, "UPPER://domain:@#$56!/")); - } - - { - std::string const strUrl("s://" + std::string(8192, ':')); - ParsedUrl pUrl; - BEAST_EXPECT(!parseUrl(pUrl, strUrl)); - } - } - - void - testToString() - { - testcase("toString"); - auto result = to_string("hello"); - BEAST_EXPECT(result == "hello"); - } - - void - run() override - { - testParseUrl(); - testUnHex(); - testToString(); - } -}; - -BEAST_DEFINE_TESTSUITE(StringUtilities, basics, xrpl); - -} // namespace xrpl diff --git a/src/test/basics/TaggedCache_test.cpp b/src/test/basics/TaggedCache_test.cpp deleted file mode 100644 index 26564a4de8..0000000000 --- a/src/test/basics/TaggedCache_test.cpp +++ /dev/null @@ -1,251 +0,0 @@ -#include - -#include -#include -#include -#include // IWYU pragma: keep -#include -#include -#include -#include - -#include -#include - -namespace xrpl { - -/* -I guess you can put some items in, make sure they're still there. Let some -time pass, make sure they're gone. Keep a strong pointer to one of them, make -sure you can still find it even after time passes. Create two objects with -the same key, canonicalize them both and make sure you get the same object. -Put an object in but keep a strong pointer to it, advance the clock a lot, -then canonicalize a new object with the same key, make sure you get the -original object. -*/ - -class TaggedCache_test : public beast::unit_test::Suite -{ -public: - void - run() override - { - using namespace std::chrono_literals; - using beast::Severity; - test::SuiteJournal journal("TaggedCache_test", *this); - - TestStopwatch clock; - clock.set(0); - - using Key = LedgerIndex; - using Value = std::string; - using Cache = TaggedCache; - - Cache c("test", 1, 1s, clock, journal); - - // Insert an item, retrieve it, and age it so it gets purged. - { - BEAST_EXPECT(c.getCacheSize() == 0); - BEAST_EXPECT(c.getTrackSize() == 0); - BEAST_EXPECT(!c.insert(1, "one")); - BEAST_EXPECT(c.getCacheSize() == 1); - BEAST_EXPECT(c.getTrackSize() == 1); - - { - std::string s; - BEAST_EXPECT(c.retrieve(1, s)); - BEAST_EXPECT(s == "one"); - } - - ++clock; - c.sweep(); - BEAST_EXPECT(c.getCacheSize() == 0); - BEAST_EXPECT(c.getTrackSize() == 0); - } - - // Insert an item, maintain a strong pointer, age it, and - // verify that the entry still exists. - { - BEAST_EXPECT(!c.insert(2, "two")); - BEAST_EXPECT(c.getCacheSize() == 1); - BEAST_EXPECT(c.getTrackSize() == 1); - - { - auto p = c.fetch(2); - BEAST_EXPECT(p != nullptr); - ++clock; - c.sweep(); - BEAST_EXPECT(c.getCacheSize() == 0); - BEAST_EXPECT(c.getTrackSize() == 1); - } - - // Make sure its gone now that our reference is gone - ++clock; - c.sweep(); - BEAST_EXPECT(c.getCacheSize() == 0); - BEAST_EXPECT(c.getTrackSize() == 0); - } - - // Insert the same key/value pair and make sure we get the same result - { - BEAST_EXPECT(!c.insert(3, "three")); - - { - auto const p1 = c.fetch(3); - auto p2 = std::make_shared("three"); - c.canonicalizeReplaceClient(3, p2); - BEAST_EXPECT(p1.get() == p2.get()); - } - ++clock; - c.sweep(); - BEAST_EXPECT(c.getCacheSize() == 0); - BEAST_EXPECT(c.getTrackSize() == 0); - } - - // Put an object in but keep a strong pointer to it, advance the clock a - // lot, then canonicalize a new object with the same key, make sure you - // get the original object. - { - // Put an object in - BEAST_EXPECT(!c.insert(4, "four")); - BEAST_EXPECT(c.getCacheSize() == 1); - BEAST_EXPECT(c.getTrackSize() == 1); - - { - // Keep a strong pointer to it - auto const p1 = c.fetch(4); - BEAST_EXPECT(p1 != nullptr); - BEAST_EXPECT(c.getCacheSize() == 1); - BEAST_EXPECT(c.getTrackSize() == 1); - // Advance the clock a lot - ++clock; - c.sweep(); - BEAST_EXPECT(c.getCacheSize() == 0); - BEAST_EXPECT(c.getTrackSize() == 1); - // Canonicalize a new object with the same key - auto p2 = std::make_shared("four"); - BEAST_EXPECT(c.canonicalizeReplaceClient(4, p2)); - BEAST_EXPECT(c.getCacheSize() == 1); - BEAST_EXPECT(c.getTrackSize() == 1); - // Make sure we get the original object - BEAST_EXPECT(p1.get() == p2.get()); - } - - ++clock; - c.sweep(); - BEAST_EXPECT(c.getCacheSize() == 0); - BEAST_EXPECT(c.getTrackSize() == 0); - } - { - BEAST_EXPECT(!c.insert(5, "five")); - BEAST_EXPECT(c.getCacheSize() == 1); - BEAST_EXPECT(c.size() == 1); - - { - auto const p1 = c.fetch(5); - BEAST_EXPECT(p1 != nullptr); - BEAST_EXPECT(c.getCacheSize() == 1); - BEAST_EXPECT(c.size() == 1); - - // Advance the clock a lot - ++clock; - c.sweep(); - BEAST_EXPECT(c.getCacheSize() == 0); - BEAST_EXPECT(c.size() == 1); - - auto p2 = std::make_shared("five_2"); - BEAST_EXPECT(c.canonicalizeReplaceCache(5, p2)); - BEAST_EXPECT(c.getCacheSize() == 1); - BEAST_EXPECT(c.size() == 1); - // Make sure the caller's original pointer is unchanged - BEAST_EXPECT(p1.get() != p2.get()); - BEAST_EXPECT(*p2 == "five_2"); - - auto const p3 = c.fetch(5); - BEAST_EXPECT(p3 != nullptr); - BEAST_EXPECT(p3.get() == p2.get()); - BEAST_EXPECT(p3.get() != p1.get()); - } - - ++clock; - c.sweep(); - BEAST_EXPECT(c.getCacheSize() == 0); - BEAST_EXPECT(c.size() == 0); - } - - { - testcase("intrptr"); - - struct MyRefCountObject : IntrusiveRefCounts - { - std::string data; - - // Needed to support weak intrusive pointers - virtual void - partialDestructor() {}; - - MyRefCountObject() = default; - explicit MyRefCountObject(std::string data) : data(std::move(data)) - { - } - - bool - operator==(std::string const& other) const - { - return data == other; - } - }; - - using IntrPtrCache = TaggedCache< - Key, - MyRefCountObject, - /*IsKeyCache*/ false, - intr_ptr::SharedWeakUnionPtr, - intr_ptr::SharedPtr>; - - IntrPtrCache intrPtrCache("IntrPtrTest", 1, 1s, clock, journal); - - intrPtrCache.canonicalizeReplaceCache(1, intr_ptr::makeShared("one")); - BEAST_EXPECT(intrPtrCache.getCacheSize() == 1); - BEAST_EXPECT(intrPtrCache.size() == 1); - - { - { - intrPtrCache.canonicalizeReplaceCache( - 1, intr_ptr::makeShared("one_replaced")); - - auto p = intrPtrCache.fetch(1); - BEAST_EXPECT(*p == "one_replaced"); - - // Advance the clock a lot - ++clock; - intrPtrCache.sweep(); - BEAST_EXPECT(intrPtrCache.getCacheSize() == 0); - BEAST_EXPECT(intrPtrCache.size() == 1); - - intrPtrCache.canonicalizeReplaceCache( - 1, intr_ptr::makeShared("one_replaced_2")); - - auto p2 = intrPtrCache.fetch(1); - BEAST_EXPECT(*p2 == "one_replaced_2"); - - intrPtrCache.del(1, true); - } - - intrPtrCache.canonicalizeReplaceCache( - 1, intr_ptr::makeShared("one_replaced_3")); - auto p3 = intrPtrCache.fetch(1); - BEAST_EXPECT(*p3 == "one_replaced_3"); - } - - ++clock; - intrPtrCache.sweep(); - BEAST_EXPECT(intrPtrCache.getCacheSize() == 0); - BEAST_EXPECT(intrPtrCache.size() == 0); - } - } -}; - -BEAST_DEFINE_TESTSUITE(TaggedCache, basics, xrpl); - -} // namespace xrpl diff --git a/src/test/basics/Units_test.cpp b/src/test/basics/Units_test.cpp deleted file mode 100644 index dc780166c6..0000000000 --- a/src/test/basics/Units_test.cpp +++ /dev/null @@ -1,344 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace xrpl::test { - -class units_test : public beast::unit_test::Suite -{ -private: - void - testTypes() - { - using FeeLevel32 = FeeLevel; - - { - XRPAmount const x{100}; - BEAST_EXPECT(x.drops() == 100); - BEAST_EXPECT((std::is_same_v)); - auto y = 4u * x; - BEAST_EXPECT(y.value() == 400); - BEAST_EXPECT((std::is_same_v)); - - auto z = 4 * y; - BEAST_EXPECT(z.value() == 1600); - BEAST_EXPECT((std::is_same_v)); - - FeeLevel32 const f{10}; - FeeLevel32 const baseFee{100}; - - auto drops = mulDiv(baseFee, x, f); - - BEAST_EXPECT(drops); - BEAST_EXPECT(drops.value() == 1000); // NOLINT(bugprone-unchecked-optional-access) - BEAST_EXPECT((std::is_same_v< - std::remove_reference_t::unit_type, - unit::dropTag>)); - - BEAST_EXPECT((std::is_same_v, XRPAmount>)); - } - { - XRPAmount const x{100}; - BEAST_EXPECT(x.value() == 100); - BEAST_EXPECT((std::is_same_v)); - auto y = 4u * x; - BEAST_EXPECT(y.value() == 400); - BEAST_EXPECT((std::is_same_v)); - - FeeLevel64 const f{10}; - FeeLevel64 const baseFee{100}; - - auto drops = mulDiv(baseFee, x, f); - - BEAST_EXPECT(drops); - BEAST_EXPECT(drops.value() == 1000); // NOLINT(bugprone-unchecked-optional-access) - BEAST_EXPECT((std::is_same_v< - std::remove_reference_t::unit_type, - unit::dropTag>)); - BEAST_EXPECT((std::is_same_v, XRPAmount>)); - } - { - FeeLevel64 const x{1024}; - BEAST_EXPECT(x.value() == 1024); - BEAST_EXPECT((std::is_same_v)); - std::uint64_t const m = 4; - auto y = m * x; - BEAST_EXPECT(y.value() == 4096); - BEAST_EXPECT((std::is_same_v)); - - XRPAmount const basefee{10}; - FeeLevel64 const referencefee{256}; - - auto drops = mulDiv(x, basefee, referencefee); - - BEAST_EXPECT(drops); - BEAST_EXPECT(drops.value() == 40); // NOLINT(bugprone-unchecked-optional-access) - BEAST_EXPECT((std::is_same_v< - std::remove_reference_t::unit_type, - unit::dropTag>)); - BEAST_EXPECT((std::is_same_v, XRPAmount>)); - } - } - - void - testJson() - { - // Json value functionality - using FeeLevel32 = FeeLevel; - - { - FeeLevel32 const x{std::numeric_limits::max()}; - auto y = x.jsonClipped(); - BEAST_EXPECT(y.type() == json::ValueType::UInt); - BEAST_EXPECT(y == json::Value{x.fee()}); - } - - { - FeeLevel32 const x{std::numeric_limits::min()}; - auto y = x.jsonClipped(); - BEAST_EXPECT(y.type() == json::ValueType::UInt); - BEAST_EXPECT(y == json::Value{x.fee()}); - } - - { - FeeLevel64 const x{std::numeric_limits::max()}; - auto y = x.jsonClipped(); - BEAST_EXPECT(y.type() == json::ValueType::UInt); - BEAST_EXPECT(y == json::Value{std::numeric_limits::max()}); - } - - { - FeeLevel64 const x{std::numeric_limits::min()}; - auto y = x.jsonClipped(); - BEAST_EXPECT(y.type() == json::ValueType::UInt); - BEAST_EXPECT(y == json::Value{0}); - } - - { - FeeLevelDouble const x{std::numeric_limits::max()}; - auto y = x.jsonClipped(); - BEAST_EXPECT(y.type() == json::ValueType::Real); - BEAST_EXPECT(y == json::Value{std::numeric_limits::max()}); - } - - { - FeeLevelDouble const x{std::numeric_limits::min()}; - auto y = x.jsonClipped(); - BEAST_EXPECT(y.type() == json::ValueType::Real); - BEAST_EXPECT(y == json::Value{std::numeric_limits::min()}); - } - - { - XRPAmount const x{std::numeric_limits::max()}; - auto y = x.jsonClipped(); - BEAST_EXPECT(y.type() == json::ValueType::Int); - BEAST_EXPECT(y == json::Value{std::numeric_limits::max()}); - } - - { - XRPAmount const x{std::numeric_limits::min()}; - auto y = x.jsonClipped(); - BEAST_EXPECT(y.type() == json::ValueType::Int); - BEAST_EXPECT(y == json::Value{std::numeric_limits::min()}); - } - } - - void - testFunctions() - { - // Explicitly test every defined function for the ValueUnit class - // since some of them are templated, but not used anywhere else. - using FeeLevel32 = FeeLevel; - - { - auto make = [&](auto x) -> FeeLevel64 { return x; }; - auto explicitmake = [&](auto x) -> FeeLevel64 { return FeeLevel64{x}; }; - - [[maybe_unused]] - FeeLevel64 const defaulted{}; - FeeLevel64 test{0}; - BEAST_EXPECT(test.fee() == 0); - - test = explicitmake(beast::kZero); - BEAST_EXPECT(test.fee() == 0); - - test = beast::kZero; - BEAST_EXPECT(test.fee() == 0); - - test = explicitmake(100u); - BEAST_EXPECT(test.fee() == 100); - - FeeLevel64 const targetSame{200u}; - FeeLevel32 const targetOther{300u}; - test = make(targetSame); - BEAST_EXPECT(test.fee() == 200); - BEAST_EXPECT(test == targetSame); - BEAST_EXPECT(test < FeeLevel64{1000}); - BEAST_EXPECT(test > FeeLevel64{100}); - test = make(targetOther); - BEAST_EXPECT(test.fee() == 300); - BEAST_EXPECT(test == targetOther); - - test = std::uint64_t(200); - BEAST_EXPECT(test.fee() == 200); - test = std::uint32_t(300); - BEAST_EXPECT(test.fee() == 300); - - test = targetSame; - BEAST_EXPECT(test.fee() == 200); - test = targetOther.fee(); - BEAST_EXPECT(test.fee() == 300); - BEAST_EXPECT(test == targetOther); - - test = targetSame * 2; - BEAST_EXPECT(test.fee() == 400); - test = 3 * targetSame; - BEAST_EXPECT(test.fee() == 600); - test = targetSame / 10; - BEAST_EXPECT(test.fee() == 20); - - test += targetSame; - BEAST_EXPECT(test.fee() == 220); - - test -= targetSame; - BEAST_EXPECT(test.fee() == 20); - - test++; - BEAST_EXPECT(test.fee() == 21); - ++test; - BEAST_EXPECT(test.fee() == 22); - test--; - BEAST_EXPECT(test.fee() == 21); - --test; - BEAST_EXPECT(test.fee() == 20); - - test *= 5; - BEAST_EXPECT(test.fee() == 100); - test /= 2; - BEAST_EXPECT(test.fee() == 50); - test %= 13; - BEAST_EXPECT(test.fee() == 11); - - /* - // illegal with unsigned - test = -test; - BEAST_EXPECT(test.fee() == -11); - BEAST_EXPECT(test.signum() == -1); - BEAST_EXPECT(to_string(test) == "-11"); - */ - - BEAST_EXPECT(test); - test = 0; - BEAST_EXPECT(!test); - BEAST_EXPECT(test.signum() == 0); - test = targetSame; - BEAST_EXPECT(test.signum() == 1); - BEAST_EXPECT(to_string(test) == "200"); - } - { - auto make = [&](auto x) -> FeeLevelDouble { return x; }; - auto explicitmake = [&](auto x) -> FeeLevelDouble { return FeeLevelDouble{x}; }; - - [[maybe_unused]] - FeeLevelDouble const defaulted{}; - FeeLevelDouble test{0}; - BEAST_EXPECT(test.fee() == 0); - - test = explicitmake(beast::kZero); - BEAST_EXPECT(test.fee() == 0); - - test = beast::kZero; - BEAST_EXPECT(test.fee() == 0); - - test = explicitmake(100.0); - BEAST_EXPECT(test.fee() == 100); - - FeeLevelDouble const targetSame{200.0}; - FeeLevel64 const targetOther{300}; - test = make(targetSame); - BEAST_EXPECT(test.fee() == 200); - BEAST_EXPECT(test == targetSame); - BEAST_EXPECT(test < FeeLevelDouble{1000.0}); - BEAST_EXPECT(test > FeeLevelDouble{100.0}); - test = targetOther.fee(); - BEAST_EXPECT(test.fee() == 300); - BEAST_EXPECT(test == targetOther); - - test = 200.0; - BEAST_EXPECT(test.fee() == 200); - test = std::uint64_t(300); - BEAST_EXPECT(test.fee() == 300); - - test = targetSame; - BEAST_EXPECT(test.fee() == 200); - - test = targetSame * 2; - BEAST_EXPECT(test.fee() == 400); - test = 3 * targetSame; - BEAST_EXPECT(test.fee() == 600); - test = targetSame / 10; - BEAST_EXPECT(test.fee() == 20); - - test += targetSame; - BEAST_EXPECT(test.fee() == 220); - - test -= targetSame; - BEAST_EXPECT(test.fee() == 20); - - test++; - BEAST_EXPECT(test.fee() == 21); - ++test; - BEAST_EXPECT(test.fee() == 22); - test--; - BEAST_EXPECT(test.fee() == 21); - --test; - BEAST_EXPECT(test.fee() == 20); - - test *= 5; - BEAST_EXPECT(test.fee() == 100); - test /= 2; - BEAST_EXPECT(test.fee() == 50); - /* illegal with floating - test %= 13; - BEAST_EXPECT(test.fee() == 11); - */ - - // legal with signed - test = -test; - BEAST_EXPECT(test.fee() == -50); - BEAST_EXPECT(test.signum() == -1); - BEAST_EXPECT(to_string(test) == "-50.000000"); - - BEAST_EXPECT(test); - test = 0; - BEAST_EXPECT(!test); - BEAST_EXPECT(test.signum() == 0); - test = targetSame; - BEAST_EXPECT(test.signum() == 1); - BEAST_EXPECT(to_string(test) == "200.000000"); - } - } - -public: - void - run() override - { - BEAST_EXPECT(kInitialXrp.drops() == 100'000'000'000'000'000); - BEAST_EXPECT(kInitialXrp == XRPAmount{100'000'000'000'000'000}); - - testTypes(); - testJson(); - testFunctions(); - } -}; - -BEAST_DEFINE_TESTSUITE(units, basics, xrpl); - -} // namespace xrpl::test diff --git a/src/test/basics/XRPAmount_test.cpp b/src/test/basics/XRPAmount_test.cpp deleted file mode 100644 index f393003365..0000000000 --- a/src/test/basics/XRPAmount_test.cpp +++ /dev/null @@ -1,326 +0,0 @@ -#include -#include -#include - -#include -#include - -namespace xrpl { - -class XRPAmount_test : public beast::unit_test::Suite -{ -public: - void - testSigNum() - { - testcase("signum"); - - for (auto i : {-1, 0, 1}) - { - XRPAmount const x(i); - - if (i < 0) - { - BEAST_EXPECT(x.signum() < 0); - } - else if (i > 0) - { - BEAST_EXPECT(x.signum() > 0); - } - else - { - BEAST_EXPECT(x.signum() == 0); - } - } - } - - void - testBeastZero() - { - testcase("beast::Zero Comparisons"); - - using beast::kZero; - - for (auto i : {-1, 0, 1}) - { - XRPAmount const x(i); - - BEAST_EXPECT((i == 0) == (x == kZero)); - BEAST_EXPECT((i != 0) == (x != kZero)); - BEAST_EXPECT((i < 0) == (x < kZero)); - BEAST_EXPECT((i > 0) == (x > kZero)); - BEAST_EXPECT((i <= 0) == (x <= kZero)); - BEAST_EXPECT((i >= 0) == (x >= kZero)); - - BEAST_EXPECT((0 == i) == (kZero == x)); - BEAST_EXPECT((0 != i) == (kZero != x)); - BEAST_EXPECT((0 < i) == (kZero < x)); - BEAST_EXPECT((0 > i) == (kZero > x)); - BEAST_EXPECT((0 <= i) == (kZero <= x)); - BEAST_EXPECT((0 >= i) == (kZero >= x)); - } - } - - void - testComparisons() - { - testcase("XRP Comparisons"); - - for (auto i : {-1, 0, 1}) - { - XRPAmount const x(i); - - for (auto j : {-1, 0, 1}) - { - XRPAmount const y(j); - - BEAST_EXPECT((i == j) == (x == y)); - BEAST_EXPECT((i != j) == (x != y)); - BEAST_EXPECT((i < j) == (x < y)); - BEAST_EXPECT((i > j) == (x > y)); - BEAST_EXPECT((i <= j) == (x <= y)); - BEAST_EXPECT((i >= j) == (x >= y)); - } - } - } - - void - testAddSub() - { - testcase("Addition & Subtraction"); - - for (auto i : {-1, 0, 1}) - { - XRPAmount const x(i); - - for (auto j : {-1, 0, 1}) - { - XRPAmount const y(j); - - BEAST_EXPECT(XRPAmount(i + j) == (x + y)); - BEAST_EXPECT(XRPAmount(i - j) == (x - y)); - - BEAST_EXPECT((x + y) == (y + x)); // addition is commutative - } - } - } - - void - testDecimal() - { - // Tautology - BEAST_EXPECT(kDropsPerXrp.decimalXRP() == 1); - - XRPAmount test{1}; - BEAST_EXPECT(test.decimalXRP() == 0.000001); - - test = -test; - BEAST_EXPECT(test.decimalXRP() == -0.000001); - - test = 100'000'000; - BEAST_EXPECT(test.decimalXRP() == 100); - - test = -test; - BEAST_EXPECT(test.decimalXRP() == -100); - } - - void - testFunctions() - { - // Explicitly test every defined function for the XRPAmount class - // since some of them are templated, but not used anywhere else. - auto make = [&](auto x) -> XRPAmount { return XRPAmount{x}; }; - - XRPAmount const defaulted{}; - (void)defaulted; - XRPAmount test{0}; - BEAST_EXPECT(test.drops() == 0); - - test = make(beast::kZero); - BEAST_EXPECT(test.drops() == 0); - - test = beast::kZero; - BEAST_EXPECT(test.drops() == 0); - - test = make(100); - BEAST_EXPECT(test.drops() == 100); - - test = make(100u); - BEAST_EXPECT(test.drops() == 100); - - XRPAmount const targetSame{200u}; - test = make(targetSame); - BEAST_EXPECT(test.drops() == 200); - BEAST_EXPECT(test == targetSame); - BEAST_EXPECT(test < XRPAmount{1000}); - BEAST_EXPECT(test > XRPAmount{100}); - - test = std::int64_t(200); - BEAST_EXPECT(test.drops() == 200); - test = std::uint32_t(300); - BEAST_EXPECT(test.drops() == 300); - - test = targetSame; - BEAST_EXPECT(test.drops() == 200); - auto testOther = test.dropsAs(); - BEAST_EXPECT(testOther); - BEAST_EXPECT(*testOther == 200); // NOLINT(bugprone-unchecked-optional-access) - test = std::numeric_limits::max(); - testOther = test.dropsAs(); - BEAST_EXPECT(!testOther); - test = -1; - testOther = test.dropsAs(); - BEAST_EXPECT(!testOther); - - test = targetSame * 2; - BEAST_EXPECT(test.drops() == 400); - test = 3 * targetSame; - BEAST_EXPECT(test.drops() == 600); - test = 20; - BEAST_EXPECT(test.drops() == 20); - - test += targetSame; - BEAST_EXPECT(test.drops() == 220); - - test -= targetSame; - BEAST_EXPECT(test.drops() == 20); - - test *= 5; - BEAST_EXPECT(test.drops() == 100); - test = 50; - BEAST_EXPECT(test.drops() == 50); - test -= 39; - BEAST_EXPECT(test.drops() == 11); - - // legal with signed - test = -test; - BEAST_EXPECT(test.drops() == -11); - BEAST_EXPECT(test.signum() == -1); - BEAST_EXPECT(to_string(test) == "-11"); - - BEAST_EXPECT(test); - test = 0; - BEAST_EXPECT(!test); - BEAST_EXPECT(test.signum() == 0); - test = targetSame; - BEAST_EXPECT(test.signum() == 1); - BEAST_EXPECT(to_string(test) == "200"); - } - - void - testMulRatio() - { - testcase("mulRatio"); - - constexpr auto kMaxUInt32 = std::numeric_limits::max(); - constexpr auto kMaxXrp = std::numeric_limits::max(); - constexpr auto kMinXrp = std::numeric_limits::min(); - - { - // multiply by a number that would overflow then divide by the same - // number, and check we didn't lose any value - XRPAmount big(kMaxXrp); - BEAST_EXPECT(big == mulRatio(big, kMaxUInt32, kMaxUInt32, true)); - // rounding mode shouldn't matter as the result is exact - BEAST_EXPECT(big == mulRatio(big, kMaxUInt32, kMaxUInt32, false)); - - // multiply and divide by values that would overflow if done - // naively, and check that it gives the correct answer - big -= 0xf; // Subtract a little so it's divisible by 4 - BEAST_EXPECT(mulRatio(big, 3, 4, false).value() == (big.value() / 4) * 3); - BEAST_EXPECT(mulRatio(big, 3, 4, true).value() == (big.value() / 4) * 3); - BEAST_EXPECT((big.value() * 3) / 4 != (big.value() / 4) * 3); - } - - { - // Similar test as above, but for negative values - XRPAmount big(kMinXrp); // NOLINT TODO - BEAST_EXPECT(big == mulRatio(big, kMaxUInt32, kMaxUInt32, true)); - // rounding mode shouldn't matter as the result is exact - BEAST_EXPECT(big == mulRatio(big, kMaxUInt32, kMaxUInt32, false)); - - // multiply and divide by values that would overflow if done - // naively, and check that it gives the correct answer - BEAST_EXPECT(mulRatio(big, 3, 4, false).value() == (big.value() / 4) * 3); - BEAST_EXPECT(mulRatio(big, 3, 4, true).value() == (big.value() / 4) * 3); - BEAST_EXPECT((big.value() * 3) / 4 != (big.value() / 4) * 3); - } - - { - // small amounts - XRPAmount const tiny(1); - // Round up should give the smallest allowable number - BEAST_EXPECT(tiny == mulRatio(tiny, 1, kMaxUInt32, true)); - // rounding down should be zero - BEAST_EXPECT(beast::kZero == mulRatio(tiny, 1, kMaxUInt32, false)); - BEAST_EXPECT(beast::kZero == mulRatio(tiny, kMaxUInt32 - 1, kMaxUInt32, false)); - - // tiny negative numbers - XRPAmount const tinyNeg(-1); - // Round up should give zero - BEAST_EXPECT(beast::kZero == mulRatio(tinyNeg, 1, kMaxUInt32, true)); - BEAST_EXPECT(beast::kZero == mulRatio(tinyNeg, kMaxUInt32 - 1, kMaxUInt32, true)); - // rounding down should be tiny - BEAST_EXPECT(tinyNeg == mulRatio(tinyNeg, kMaxUInt32 - 1, kMaxUInt32, false)); - } - - { // rounding - { - XRPAmount const one(1); - auto const rup = mulRatio(one, kMaxUInt32 - 1, kMaxUInt32, true); - auto const rdown = mulRatio(one, kMaxUInt32 - 1, kMaxUInt32, false); - BEAST_EXPECT(rup.drops() - rdown.drops() == 1); - } - - { - XRPAmount const big(kMaxXrp); - auto const rup = mulRatio(big, kMaxUInt32 - 1, kMaxUInt32, true); - auto const rdown = mulRatio(big, kMaxUInt32 - 1, kMaxUInt32, false); - BEAST_EXPECT(rup.drops() - rdown.drops() == 1); - } - - { - XRPAmount const negOne(-1); - auto const rup = mulRatio(negOne, kMaxUInt32 - 1, kMaxUInt32, true); - auto const rdown = mulRatio(negOne, kMaxUInt32 - 1, kMaxUInt32, false); - BEAST_EXPECT(rup.drops() - rdown.drops() == 1); - } - } - - { - // division by zero - XRPAmount one(1); - except([&] { mulRatio(one, 1, 0, true); }); - } - - { - // overflow - XRPAmount big(kMaxXrp); - except([&] { mulRatio(big, 2, 1, true); }); - } - - { - // underflow - XRPAmount const bigNegative(kMinXrp + 10); - BEAST_EXPECT(mulRatio(bigNegative, 2, 1, true) == kMinXrp); - } - } // namespace xrpl - - //-------------------------------------------------------------------------- - - void - run() override - { - testSigNum(); - testBeastZero(); - testComparisons(); - testAddSub(); - testDecimal(); - testFunctions(); - testMulRatio(); - } -}; - -BEAST_DEFINE_TESTSUITE(XRPAmount, basics, xrpl); - -} // namespace xrpl diff --git a/src/test/basics/base58_test.cpp b/src/test/basics/base58_test.cpp deleted file mode 100644 index d2c8d9eabc..0000000000 --- a/src/test/basics/base58_test.cpp +++ /dev/null @@ -1,440 +0,0 @@ -#include -#include - -#include // IWYU pragma: keep - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifndef _MSC_VER - -#include -#include - -#include -#include -#include -#include -#include - -namespace xrpl::test { -namespace { - -[[nodiscard]] inline auto -randEngine() -> std::mt19937& -{ - static std::mt19937 kR = [] { - std::random_device rd; - return std::mt19937{rd()}; - }(); - return kR; -} - -constexpr int kNumTokenTypeIndexes = 9; - -[[nodiscard]] inline auto -tokenTypeAndSize(int i) -> std::tuple -{ - assert(i < kNumTokenTypeIndexes); - - switch (i) - { - using enum xrpl::TokenType; - case 0: - return {None, 20}; - case 1: - return {NodePublic, 32}; - case 2: - return {NodePublic, 33}; - case 3: - return {NodePrivate, 32}; - case 4: - return {AccountID, 20}; - case 5: - return {AccountPublic, 32}; - case 6: - return {AccountPublic, 33}; - case 7: - return {AccountSecret, 32}; - case 8: - return {FamilySeed, 16}; - default: - throw std::invalid_argument( - "Invalid token selection passed to tokenTypeAndSize() " - "in " __FILE__); - } -} - -[[nodiscard]] inline auto -randomTokenTypeAndSize() -> std::tuple -{ - using namespace xrpl; - auto& rng = randEngine(); - std::uniform_int_distribution<> d(0, 8); - return tokenTypeAndSize(d(rng)); -} - -// Return the token type and subspan of `d` to use as test data. -[[nodiscard]] inline auto -randomB256TestData(std::span d) - -> std::tuple> -{ - auto& rng = randEngine(); - std::uniform_int_distribution dist(0, 255); - auto [tokType, tokSize] = randomTokenTypeAndSize(); - std::generate(d.begin(), d.begin() + tokSize, [&] { return dist(rng); }); - return {tokType, d.subspan(0, tokSize)}; -} - -inline void -printAsChar(std::span a, std::span b) -{ - auto asString = [](std::span s) { - std::string r; - r.resize(s.size()); - std::ranges::copy(s, r.begin()); - return r; - }; - auto sa = asString(a); - auto sb = asString(b); - std::cerr << "\n\n" << sa << "\n" << sb << "\n"; -} - -inline void -printAsInt(std::span a, std::span b) -{ - auto asString = [](std::span s) -> std::string { - std::stringstream sstr; - for (auto i : s) - { - sstr << std::setw(3) << int(i) << ','; - } - return sstr.str(); - }; - auto sa = asString(a); - auto sb = asString(b); - std::cerr << "\n\n" << sa << "\n" << sb << "\n"; -} - -} // namespace - -namespace multiprecision_utils { - -boost::multiprecision::checked_uint512_t -toBoostMP(std::span in) -{ - boost::multiprecision::checked_uint512_t mbp = 0; - for (auto& word : std::views::reverse(in)) - { - mbp <<= 64; - mbp += word; - } - return mbp; -} - -std::vector -randomBigInt(std::uint8_t minSize = 1, std::uint8_t maxSize = 5) -{ - auto eng = randEngine(); - std::uniform_int_distribution numCoeffDist(minSize, maxSize); - std::uniform_int_distribution dist; - auto const numCoeff = numCoeffDist(eng); - std::vector coeffs; - coeffs.reserve(numCoeff); - for (int i = 0; i < numCoeff; ++i) - { - coeffs.push_back(dist(eng)); - } - return coeffs; -} -} // namespace multiprecision_utils - -class base58_test : public beast::unit_test::Suite -{ - void - testMultiprecision() - { - testcase("b58_multiprecision"); - - using namespace boost::multiprecision; - - static constexpr std::size_t kIters = 100000; - auto eng = randEngine(); - std::uniform_int_distribution dist; - std::uniform_int_distribution dist1(1); - for (int i = 0; i < kIters; ++i) - { - std::uint64_t const d = dist(eng); - if (d == 0u) - continue; - auto bigInt = multiprecision_utils::randomBigInt(); - auto const boostBigInt = multiprecision_utils::toBoostMP( - std::span(bigInt.data(), bigInt.size())); - - auto const refDiv = boostBigInt / d; - auto const refMod = boostBigInt % d; - - auto const mod = b58_fast::detail::inplaceBigintDivRem( - std::span(bigInt.data(), bigInt.size()), d); - auto const foundDiv = multiprecision_utils::toBoostMP(bigInt); - BEAST_EXPECT(refMod.convert_to() == mod); - BEAST_EXPECT(foundDiv == refDiv); - } - for (int i = 0; i < kIters; ++i) - { - std::uint64_t const d = dist(eng); - auto bigInt = multiprecision_utils::randomBigInt(/*minSize*/ 2); - if (bigInt[bigInt.size() - 1] == std::numeric_limits::max()) - { - bigInt[bigInt.size() - 1] -= 1; // Prevent overflow - } - auto const boostBigInt = multiprecision_utils::toBoostMP( - std::span(bigInt.data(), bigInt.size())); - - auto const refAdd = boostBigInt + d; - - auto const result = b58_fast::detail::inplaceBigintAdd( - std::span(bigInt.data(), bigInt.size()), d); - BEAST_EXPECT(result == TokenCodecErrc::Success); - auto const foundAdd = multiprecision_utils::toBoostMP(bigInt); - BEAST_EXPECT(refAdd == foundAdd); - } - for (int i = 0; i < kIters; ++i) - { - std::uint64_t const d = dist1(eng); - // Force overflow - std::vector bigInt(5, std::numeric_limits::max()); - - auto const boostBigInt = multiprecision_utils::toBoostMP( - std::span(bigInt.data(), bigInt.size())); - - auto const refAdd = boostBigInt + d; - - auto const result = b58_fast::detail::inplaceBigintAdd( - std::span(bigInt.data(), bigInt.size()), d); - BEAST_EXPECT(result == TokenCodecErrc::OverflowAdd); - auto const foundAdd = multiprecision_utils::toBoostMP(bigInt); - BEAST_EXPECT(refAdd != foundAdd); - } - for (int i = 0; i < kIters; ++i) - { - std::uint64_t const d = dist(eng); - auto bigInt = multiprecision_utils::randomBigInt(/* minSize */ 2); - // inplace mul requires the most significant coeff to be zero to - // hold the result. - bigInt[bigInt.size() - 1] = 0; - auto const boostBigInt = multiprecision_utils::toBoostMP( - std::span(bigInt.data(), bigInt.size())); - - auto const refMul = boostBigInt * d; - - auto const result = b58_fast::detail::inplaceBigintMul( - std::span(bigInt.data(), bigInt.size()), d); - BEAST_EXPECT(result == TokenCodecErrc::Success); - auto const foundMul = multiprecision_utils::toBoostMP(bigInt); - BEAST_EXPECT(refMul == foundMul); - } - for (int i = 0; i < kIters; ++i) - { - std::uint64_t const d = dist1(eng); - // Force overflow - std::vector bigInt(5, std::numeric_limits::max()); - auto const boostBigInt = multiprecision_utils::toBoostMP( - std::span(bigInt.data(), bigInt.size())); - - auto const refMul = boostBigInt * d; - - auto const result = b58_fast::detail::inplaceBigintMul( - std::span(bigInt.data(), bigInt.size()), d); - BEAST_EXPECT(result == TokenCodecErrc::InputTooLarge); - auto const foundMul = multiprecision_utils::toBoostMP(bigInt); - BEAST_EXPECT(refMul != foundMul); - } - } - - void - testFastMatchesRef() - { - testcase("fast_matches_ref"); - auto testRawEncode = [&](std::span const& b256Data) { - std::array b58ResultBuf[2]; - std::array, 2> b58Result; - - std::array b256ResultBuf[2]; - std::array, 2> b256Result; - for (int i = 0; i < 2; ++i) - { - std::span const outBuf{b58ResultBuf[i]}; - if (i == 0) - { - auto const r = xrpl::b58_fast::detail::b256ToB58Be(b256Data, outBuf); - BEAST_EXPECT(r); - b58Result[i] = r.value(); - } - else - { - std::array tmpBuf{}; - std::string const s = xrpl::b58_ref::detail::encodeBase58( - b256Data.data(), b256Data.size(), tmpBuf.data(), tmpBuf.size()); - BEAST_EXPECT(s.size()); - b58Result[i] = outBuf.subspan(0, s.size()); - std::ranges::copy(s, b58Result[i].begin()); - } - } - if (BEAST_EXPECT(b58Result[0].size() == b58Result[1].size())) - { - if (!BEAST_EXPECT( - memcmp(b58Result[0].data(), b58Result[1].data(), b58Result[0].size()) == 0)) - { - printAsChar(b58Result[0], b58Result[1]); - } - } - - for (int i = 0; i < 2; ++i) - { - std::span const outBuf{b256ResultBuf[i].data(), b256ResultBuf[i].size()}; - if (i == 0) - { - std::string const in( - b58Result[i].data(), b58Result[i].data() + b58Result[i].size()); - auto const r = xrpl::b58_fast::detail::b58ToB256Be(in, outBuf); - BEAST_EXPECT(r); - b256Result[i] = r.value(); - } - else - { - std::string const st(b58Result[i].begin(), b58Result[i].end()); - std::string const s = xrpl::b58_ref::detail::decodeBase58(st); - BEAST_EXPECT(s.size()); - b256Result[i] = outBuf.subspan(0, s.size()); - std::ranges::copy(s, b256Result[i].begin()); - } - } - - if (BEAST_EXPECT(b256Result[0].size() == b256Result[1].size())) - { - if (!BEAST_EXPECT( - memcmp(b256Result[0].data(), b256Result[1].data(), b256Result[0].size()) == - 0)) - { - printAsInt(b256Result[0], b256Result[1]); - } - } - }; - - auto testTokenEncode = [&](xrpl::TokenType const tokType, - std::span const& b256Data) { - std::array b58ResultBuf[2]; - std::array, 2> b58Result; - - std::array b256ResultBuf[2]; - std::array, 2> b256Result; - for (int i = 0; i < 2; ++i) - { - std::span const outBuf{b58ResultBuf[i].data(), b58ResultBuf[i].size()}; - if (i == 0) - { - auto const r = xrpl::b58_fast::encodeBase58Token(tokType, b256Data, outBuf); - BEAST_EXPECT(r); - b58Result[i] = r.value(); - } - else - { - std::string const s = - xrpl::b58_ref::encodeBase58Token(tokType, b256Data.data(), b256Data.size()); - BEAST_EXPECT(s.size()); - b58Result[i] = outBuf.subspan(0, s.size()); - std::ranges::copy(s, b58Result[i].begin()); - } - } - if (BEAST_EXPECT(b58Result[0].size() == b58Result[1].size())) - { - if (!BEAST_EXPECT( - memcmp(b58Result[0].data(), b58Result[1].data(), b58Result[0].size()) == 0)) - { - printAsChar(b58Result[0], b58Result[1]); - } - } - - for (int i = 0; i < 2; ++i) - { - std::span const outBuf{b256ResultBuf[i].data(), b256ResultBuf[i].size()}; - if (i == 0) - { - std::string const in( - b58Result[i].data(), b58Result[i].data() + b58Result[i].size()); - auto const r = xrpl::b58_fast::decodeBase58Token(tokType, in, outBuf); - BEAST_EXPECT(r); - b256Result[i] = r.value(); - } - else - { - std::string const st(b58Result[i].begin(), b58Result[i].end()); - std::string const s = xrpl::b58_ref::decodeBase58Token(st, tokType); - BEAST_EXPECT(s.size()); - b256Result[i] = outBuf.subspan(0, s.size()); - std::ranges::copy(s, b256Result[i].begin()); - } - } - - if (BEAST_EXPECT(b256Result[0].size() == b256Result[1].size())) - { - if (!BEAST_EXPECT( - memcmp(b256Result[0].data(), b256Result[1].data(), b256Result[0].size()) == - 0)) - { - printAsInt(b256Result[0], b256Result[1]); - } - } - }; - - auto testIt = [&](xrpl::TokenType const tokType, std::span const& b256Data) { - testRawEncode(b256Data); - testTokenEncode(tokType, b256Data); - }; - - // test every token type with data where every byte is the same and the - // bytes range from 0-255 - for (int i = 0; i < kNumTokenTypeIndexes; ++i) - { - std::array b256DataBuf{}; - auto const [tokType, tokSize] = tokenTypeAndSize(i); - for (int d = 0; d <= 255; ++d) - { - memset(b256DataBuf.data(), d, tokSize); - testIt(tokType, std::span(b256DataBuf.data(), tokSize)); - } - } - - // test with random data - static constexpr std::size_t kIters = 100000; - for (int i = 0; i < kIters; ++i) - { - std::array b256DataBuf{}; - auto const [tokType, b256Data] = randomB256TestData(b256DataBuf); - testIt(tokType, b256Data); - } - } - - void - run() override - { - testMultiprecision(); - testFastMatchesRef(); - } -}; - -BEAST_DEFINE_TESTSUITE(base58, basics, xrpl); - -} // namespace xrpl::test - -#endif // _MSC_VER diff --git a/src/test/basics/base_uint_test.cpp b/src/test/basics/base_uint_test.cpp deleted file mode 100644 index c6572a0028..0000000000 --- a/src/test/basics/base_uint_test.cpp +++ /dev/null @@ -1,376 +0,0 @@ -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace xrpl::test { - -// a non-hashing Hasher that just copies the bytes. -// Used to test hash_append in base_uint -template -struct Nonhash -{ - static constexpr auto kEndian = boost::endian::order::big; - static constexpr std::size_t kWidth = Bits / 8; - - std::array data; - - Nonhash() = default; - - void - operator()(void const* key, std::size_t len) noexcept - { - assert(len == kWidth); - memcpy(data.data(), key, len); - } - - explicit - operator std::size_t() noexcept - { - return kWidth; - } -}; - -struct base_uint_test : beast::unit_test::Suite -{ - using test96 = BaseUInt<96>; - static_assert(std::is_copy_constructible_v); - static_assert(std::is_copy_assignable_v); - - void - testComparisons() - { - { - static constexpr std::array, 6> kTestArgs{ - {{"0000000000000000", "0000000000000001"}, - {"0000000000000000", "ffffffffffffffff"}, - {"1234567812345678", "2345678923456789"}, - {"8000000000000000", "8000000000000001"}, - {"aaaaaaaaaaaaaaa9", "aaaaaaaaaaaaaaaa"}, - {"fffffffffffffffe", "ffffffffffffffff"}}}; - - for (auto const& arg : kTestArgs) - { - xrpl::BaseUInt<64> const u{arg.first}, v{arg.second}; - BEAST_EXPECT(u < v); - BEAST_EXPECT(u <= v); - BEAST_EXPECT(u != v); - BEAST_EXPECT(!(u == v)); - BEAST_EXPECT(!(u > v)); - BEAST_EXPECT(!(u >= v)); - BEAST_EXPECT(!(v < u)); - BEAST_EXPECT(!(v <= u)); - BEAST_EXPECT(v != u); - BEAST_EXPECT(!(v == u)); - BEAST_EXPECT(v > u); - BEAST_EXPECT(v >= u); - BEAST_EXPECT(u == u); - BEAST_EXPECT(v == v); - } - } - - { - static constexpr std::array, 6> kTestArgs{ - { - {"000000000000000000000000", "000000000000000000000001"}, - {"000000000000000000000000", "ffffffffffffffffffffffff"}, - {"0123456789ab0123456789ab", "123456789abc123456789abc"}, - {"555555555555555555555555", "55555555555a555555555555"}, - {"aaaaaaaaaaaaaaa9aaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaa"}, - {"fffffffffffffffffffffffe", "ffffffffffffffffffffffff"}, - }}; - - for (auto const& arg : kTestArgs) - { - xrpl::BaseUInt<96> const u{arg.first}, v{arg.second}; - BEAST_EXPECT(u < v); - BEAST_EXPECT(u <= v); - BEAST_EXPECT(u != v); - BEAST_EXPECT(!(u == v)); - BEAST_EXPECT(!(u > v)); - BEAST_EXPECT(!(u >= v)); - BEAST_EXPECT(!(v < u)); - BEAST_EXPECT(!(v <= u)); - BEAST_EXPECT(v != u); - BEAST_EXPECT(!(v == u)); - BEAST_EXPECT(v > u); - BEAST_EXPECT(v >= u); - BEAST_EXPECT(u == u); - BEAST_EXPECT(v == v); - } - } - } - - void - testFromRawSizeMismatch() - { - testcase("base_uint: fromRaw size mismatch"); - - // Container larger than the base_uint (16 bytes vs 12 bytes for test96). - // Only the first 12 bytes are copied; the extra bytes are ignored. - { - Blob const tooBig{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; - test96 const result = test96::fromRaw(tooBig); - BEAST_EXPECT(to_string(result) == "0102030405060708090A0B0C"); - } - } - - void - run() override - { - testcase("base_uint: general purpose tests"); - -#ifdef NDEBUG - testFromRawSizeMismatch(); -#endif - - static_assert(!std::is_constructible_v>); - static_assert(!std::is_assignable_v>); - - testComparisons(); - - // used to verify set insertion (hashing required) - std::unordered_set> uset; - - Blob const raw{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; - BEAST_EXPECT(test96::kBytes == raw.size()); - - test96 u = test96::fromRaw(raw); - uset.insert(u); - BEAST_EXPECT(raw.size() == u.size()); - BEAST_EXPECT(to_string(u) == "0102030405060708090A0B0C"); - BEAST_EXPECT(toShortString(u) == "01020304..."); - BEAST_EXPECT(*u.data() == 1); - BEAST_EXPECT(u.signum() == 1); - BEAST_EXPECT(!!u); - BEAST_EXPECT(!u.isZero()); - BEAST_EXPECT(u.isNonZero()); - unsigned char t = 0; - for (auto& d : u) - { - BEAST_EXPECT(d == ++t); - } - - // Test hash_append by "hashing" with a no-op hasher (h) - // and then extracting the bytes that were written during hashing - // back into another base_uint (w) for comparison with the original - Nonhash<96> h{}; - hash_append(h, u); - test96 const w = test96::fromRaw(std::vector(h.data.begin(), h.data.end())); - BEAST_EXPECT(w == u); - - test96 v{~u}; - uset.insert(v); - BEAST_EXPECT(to_string(v) == "FEFDFCFBFAF9F8F7F6F5F4F3"); - BEAST_EXPECT(toShortString(v) == "FEFDFCFB..."); - BEAST_EXPECT(*v.data() == 0xfe); - BEAST_EXPECT(v.signum() == 1); - BEAST_EXPECT(!!v); - BEAST_EXPECT(!v.isZero()); - BEAST_EXPECT(v.isNonZero()); - t = 0xff; - for (auto& d : v) - { - BEAST_EXPECT(d == --t); - } - - BEAST_EXPECT(u < v); - BEAST_EXPECT(v > u); - - v = u; - BEAST_EXPECT(v == u); - - test96 z{beast::kZero}; - uset.insert(z); - BEAST_EXPECT(to_string(z) == "000000000000000000000000"); - BEAST_EXPECT(toShortString(z) == "00000000..."); - BEAST_EXPECT(*z.data() == 0); - BEAST_EXPECT(*z.begin() == 0); - BEAST_EXPECT(*std::prev(z.end(), 1) == 0); - BEAST_EXPECT(z.signum() == 0); - BEAST_EXPECT(!z); - BEAST_EXPECT(z.isZero()); - BEAST_EXPECT(!z.isNonZero()); - for (auto& d : z) - { - BEAST_EXPECT(d == 0); - } - - test96 n{z}; - n++; - BEAST_EXPECT(n == test96(1)); - n--; - BEAST_EXPECT(n == beast::kZero); - BEAST_EXPECT(n == z); - n--; - BEAST_EXPECT(to_string(n) == "FFFFFFFFFFFFFFFFFFFFFFFF"); - BEAST_EXPECT(toShortString(n) == "FFFFFFFF..."); - n = beast::kZero; - BEAST_EXPECT(n == z); - - test96 zp1{z}; - zp1++; - test96 zm1{z}; - zm1--; - test96 const x{zm1 ^ zp1}; - uset.insert(x); - BEAST_EXPECTS(to_string(x) == "FFFFFFFFFFFFFFFFFFFFFFFE", to_string(x)); - BEAST_EXPECTS(toShortString(x) == "FFFFFFFF...", toShortString(x)); - - BEAST_EXPECT(uset.size() == 4); - - test96 tmp; - BEAST_EXPECT(tmp.parseHex(to_string(u))); - BEAST_EXPECT(tmp == u); - tmp = z; - - // fails with extra char - BEAST_EXPECT(!tmp.parseHex("A" + to_string(u))); - tmp = z; - - // fails with extra char at end - BEAST_EXPECT(!tmp.parseHex(to_string(u) + "A")); - - // fails with a non-hex character at some point in the string: - tmp = z; - - for (std::size_t i = 0; i != 24; ++i) - { - std::string x = to_string(z); - x[i] = ('G' + (i % 10)); - BEAST_EXPECT(!tmp.parseHex(x)); - } - - // Walking 1s: - for (std::size_t i = 0; i != 24; ++i) - { - std::string s1 = "000000000000000000000000"; - s1[i] = '1'; - - BEAST_EXPECT(tmp.parseHex(s1)); - BEAST_EXPECT(to_string(tmp) == s1); - } - - // Walking 0s: - for (std::size_t i = 0; i != 24; ++i) - { - std::string s1 = "111111111111111111111111"; - s1[i] = '0'; - - BEAST_EXPECT(tmp.parseHex(s1)); - BEAST_EXPECT(to_string(tmp) == s1); - } - - // Constexpr constructors - { - static_assert(test96{}.signum() == 0); - static_assert(test96("0").signum() == 0); - static_assert(test96("000000000000000000000000").signum() == 0); - static_assert(test96("000000000000000000000001").signum() == 1); - static_assert(test96("800000000000000000000000").signum() == 1); - -// Everything within the #if should fail during compilation. -#if 0 - // Too few characters - static_assert(test96("00000000000000000000000").signum() == 0); - - // Too many characters - static_assert(test96("0000000000000000000000000").signum() == 0); - - // Non-hex characters - static_assert(test96("00000000000000000000000 ").signum() == 1); - static_assert(test96("00000000000000000000000/").signum() == 1); - static_assert(test96("00000000000000000000000:").signum() == 1); - static_assert(test96("00000000000000000000000@").signum() == 1); - static_assert(test96("00000000000000000000000G").signum() == 1); - static_assert(test96("00000000000000000000000`").signum() == 1); - static_assert(test96("00000000000000000000000g").signum() == 1); - static_assert(test96("00000000000000000000000~").signum() == 1); -#endif // 0 - - // Using the constexpr constructor in a non-constexpr context - // with an error in the parsing throws an exception. - { - // Invalid length for string. - bool caught = false; - try - { - // Try to prevent constant evaluation. - std::vector str(23, '7'); - std::string_view const sView(str.data(), str.size()); - [[maybe_unused]] test96 const t96(sView); - } - catch (std::invalid_argument const& e) - { - BEAST_EXPECT(e.what() == std::string("invalid length for hex string")); - caught = true; - } - BEAST_EXPECT(caught); - } - { - // Invalid character in string. - bool caught = false; - try - { - // Try to prevent constant evaluation. - std::vector str(23, '7'); - str.push_back('G'); - std::string_view const sView(str.data(), str.size()); - [[maybe_unused]] test96 const t96(sView); - } - catch (std::range_error const& e) - { - BEAST_EXPECT(e.what() == std::string("invalid hex character")); - caught = true; - } - BEAST_EXPECT(caught); - } - - // Verify that constexpr base_uints interpret a string the same - // way parseHex() does. - struct StrBaseUInt - { - char const* const str; - test96 tst; - - constexpr StrBaseUInt(char const* s) : str(s), tst(s) - { - } - }; - static constexpr StrBaseUInt kTestCases[] = { - "000000000000000000000000", - "000000000000000000000001", - "fedcba9876543210ABCDEF91", - "19FEDCBA0123456789abcdef", - "800000000000000000000000", - "fFfFfFfFfFfFfFfFfFfFfFfF"}; - - for (StrBaseUInt const& t : kTestCases) - { - test96 t96; - BEAST_EXPECT(t96.parseHex(t.str)); - BEAST_EXPECT(t96 == t.tst); - } - } - } -}; - -BEAST_DEFINE_TESTSUITE(base_uint, basics, xrpl); - -} // namespace xrpl::test diff --git a/src/test/basics/join_test.cpp b/src/test/basics/join_test.cpp deleted file mode 100644 index 1ee81f786a..0000000000 --- a/src/test/basics/join_test.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace xrpl::test { - -struct join_test : beast::unit_test::Suite -{ - void - run() override - { - auto test = [this](auto collectionanddelimiter, std::string expected) { - std::stringstream ss; - // Put something else in the buffer before and after to ensure that - // the << operator returns the stream correctly. - ss << "(" << collectionanddelimiter << ")"; - auto const str = ss.str(); - BEAST_EXPECT(str.substr(1, str.length() - 2) == expected); - BEAST_EXPECT(str.front() == '('); - BEAST_EXPECT(str.back() == ')'); - }; - - // C++ array - test(CollectionAndDelimiter(std::array{2, -1, 5, 10}, "/"), "2/-1/5/10"); - // One item C++ array edge case - test(CollectionAndDelimiter(std::array{"test"}, " & "), "test"); - // Empty C++ array edge case - test(CollectionAndDelimiter(std::array{}, ","), ""); - { - // C-style array - char letters[4]{'w', 'a', 's', 'd'}; - test(CollectionAndDelimiter(letters, std::to_string(0)), "w0a0s0d"); - } - { - // Auto sized C-style array - std::string words[]{"one", "two", "three", "four"}; - test(CollectionAndDelimiter(words, "\n"), "one\ntwo\nthree\nfour"); - } - { - // One item C-style array edge case - std::string words[]{"thing"}; - test(CollectionAndDelimiter(words, "\n"), "thing"); - } - // Initializer list - test(CollectionAndDelimiter(std::initializer_list{19, 25}, "+"), "19+25"); - // vector - test(CollectionAndDelimiter(std::vector{0, 42}, std::to_string(99)), "09942"); - { - // vector with one item edge case - using namespace jtx; - test( - CollectionAndDelimiter(std::vector{Account::kMaster}, "xxx"), - Account::kMaster.human()); - } - // empty vector edge case - test(CollectionAndDelimiter(std::vector{}, ","), ""); - // C-style string - test(CollectionAndDelimiter("string", " "), "s t r i n g"); - // Empty C-style string edge case - test(CollectionAndDelimiter("", "*"), ""); - // Single char C-style string edge case - test(CollectionAndDelimiter("x", "*"), "x"); - // std::string - test(CollectionAndDelimiter(std::string{"string"}, "-"), "s-t-r-i-n-g"); - // Empty std::string edge case - test(CollectionAndDelimiter(std::string{""}, "*"), ""); - // Single char std::string edge case - test(CollectionAndDelimiter(std::string{"y"}, "*"), "y"); - } -}; // namespace test - -BEAST_DEFINE_TESTSUITE(join, basics, xrpl); - -} // namespace xrpl::test diff --git a/src/test/beast/define_print.cpp b/src/test/beast/define_print.cpp index 569b06ca67..e6b24e5cf2 100644 --- a/src/test/beast/define_print.cpp +++ b/src/test/beast/define_print.cpp @@ -15,7 +15,9 @@ namespace beast::unit_test { -/** A suite that prints the list of globally defined suites. */ +/** + * A suite that prints the list of globally defined suites. + */ class print_test : public Suite { public: diff --git a/src/test/consensus/DistributedValidatorsSim_test.cpp b/src/test/consensus/DistributedValidatorsSim_test.cpp index 1def09db13..437ad81ee0 100644 --- a/src/test/consensus/DistributedValidatorsSim_test.cpp +++ b/src/test/consensus/DistributedValidatorsSim_test.cpp @@ -21,7 +21,8 @@ namespace xrpl::test { -/** In progress simulations for diversifying and distributing validators +/** + * In progress simulations for diversifying and distributing validators */ class DistributedValidators_test : public beast::unit_test::Suite { diff --git a/src/test/consensus/LedgerTrie_test.cpp b/src/test/consensus/LedgerTrie_test.cpp index 0ddf1bf82f..a4eb7bc087 100644 --- a/src/test/consensus/LedgerTrie_test.cpp +++ b/src/test/consensus/LedgerTrie_test.cpp @@ -519,17 +519,18 @@ class LedgerTrie_test : public beast::unit_test::Suite // Changing largestSeq perspective changes preferred branch { - /** Build the tree below with initial tip support annotated - A - / \ - B(1) C(1) - / | | - H D F(1) - | - E(2) - | - G - */ + /** + * Build the tree below with initial tip support annotated + * A + * / \ + * B(1) C(1) + * / | | + * H D F(1) + * | + * E(2) + * | + * G + */ LedgerTrie t; LedgerHistoryHelper h; t.insert(h["ab"]); @@ -548,17 +549,18 @@ class LedgerTrie_test : public beast::unit_test::Suite BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["a"].id()); // NOLINTEND(bugprone-unchecked-optional-access) - /** One of E advancing to G doesn't change anything - A - / \ - B(1) C(1) - / | | - H D F(1) - | - E(1) - | - G(1) - */ + /** + * One of E advancing to G doesn't change anything + * A + * / \ + * B(1) C(1) + * / | | + * H D F(1) + * | + * E(1) + * | + * G(1) + */ t.remove(h["abde"]); t.insert(h["abdeg"]); @@ -570,17 +572,18 @@ class LedgerTrie_test : public beast::unit_test::Suite BEAST_EXPECT(t.getPreferred(Seq{5})->id == h["a"].id()); // NOLINTEND(bugprone-unchecked-optional-access) - /** C advancing to H does advance the seq 3 preferred ledger - A - / \ - B(1) C - / | | - H(1)D F(1) - | - E(1) - | - G(1) - */ + /** + * C advancing to H does advance the seq 3 preferred ledger + * A + * / \ + * B(1) C + * / | | + * H(1)D F(1) + * | + * E(1) + * | + * G(1) + */ t.remove(h["ac"]); t.insert(h["abh"]); @@ -592,17 +595,18 @@ class LedgerTrie_test : public beast::unit_test::Suite BEAST_EXPECT(t.getPreferred(Seq{5})->id == h["a"].id()); // NOLINTEND(bugprone-unchecked-optional-access) - /** F advancing to E also moves the preferred ledger forward - A - / \ - B(1) C - / | | - H(1)D F - | - E(2) - | - G(1) - */ + /** + * F advancing to E also moves the preferred ledger forward + * A + * / \ + * B(1) C + * / | | + * H(1)D F + * | + * E(2) + * | + * G(1) + */ t.remove(h["acf"]); t.insert(h["abde"]); diff --git a/src/test/core/Config_test.cpp b/src/test/core/Config_test.cpp index 38c14b8ac5..e98a0e1e88 100644 --- a/src/test/core/Config_test.cpp +++ b/src/test/core/Config_test.cpp @@ -124,7 +124,7 @@ backend=sqlite } /** - Write an xrpld config file and remove when done. + * Write an xrpld config file and remove when done. */ class FileCfgGuard : public xrpl::detail::FileDirGuard { @@ -235,7 +235,7 @@ more-xrpl-validators.net } /** - Write a validators.txt file and remove when done. + * Write a validators.txt file and remove when done. */ class ValidatorsTxtGuard : public detail::FileDirGuard { @@ -1475,6 +1475,7 @@ r.ripple.com:51235 std::string toLoad(R"xrpldConfig( [amendment_majority_time] )xrpldConfig"); + // NOLINTNEXTLINE(performance-inefficient-string-concatenation) toLoad += std::to_string(val) + space + unit; space = space.empty() ? " " : ""; diff --git a/src/test/csf/BasicNetwork.h b/src/test/csf/BasicNetwork.h index 4b88592128..0428475504 100644 --- a/src/test/csf/BasicNetwork.h +++ b/src/test/csf/BasicNetwork.h @@ -6,59 +6,59 @@ #include namespace xrpl::test::csf { -/** Peer to peer network simulator. - - The network is formed from a set of Peer objects representing - vertices and configurable connections representing edges. - The caller is responsible for creating the Peer objects ahead - of time. - - Peer objects cannot be destroyed once the BasicNetwork is - constructed. To handle peers going online and offline, - callers can simply disconnect all links and reconnect them - later. Connections are directed, one end is the inbound - Peer and the other is the outbound Peer. - - Peers may send messages along their connections. To simulate - the effects of latency, these messages can be delayed by a - configurable duration set when the link is established. - Messages always arrive in the order they were sent on a - particular connection. - - A message is modeled using a lambda function. The caller - provides the code to execute upon delivery of the message. - If a Peer is disconnected, all messages pending delivery - at either end of the connection will not be delivered. - - When creating the Peer set, the caller needs to provide a - Scheduler object for managing the timing and delivery - of messages. After constructing the network, and establishing - connections, the caller uses the scheduler's step* functions - to drive messages through the network. - - The graph of peers and connections is internally represented - using Digraph. Clients have - const access to that graph to perform additional operations not - directly provided by BasicNetwork. - - Peer Requirements: - - Peer should be a lightweight type, cheap to copy - and/or move. A good candidate is a simple pointer to - the underlying user defined type in the simulation. - - Expression Type Requirements - ---------- ---- ------------ - P Peer - u, v Values of type P - P u(v) CopyConstructible - u.~P() Destructible - u == v bool EqualityComparable - u < v bool LessThanComparable - std::hash

class std::hash is defined for P - ! u bool true if u is not-a-peer - -*/ +/** + * Peer to peer network simulator. + * + * The network is formed from a set of Peer objects representing + * vertices and configurable connections representing edges. + * The caller is responsible for creating the Peer objects ahead + * of time. + * + * Peer objects cannot be destroyed once the BasicNetwork is + * constructed. To handle peers going online and offline, + * callers can simply disconnect all links and reconnect them + * later. Connections are directed, one end is the inbound + * Peer and the other is the outbound Peer. + * + * Peers may send messages along their connections. To simulate + * the effects of latency, these messages can be delayed by a + * configurable duration set when the link is established. + * Messages always arrive in the order they were sent on a + * particular connection. + * + * A message is modeled using a lambda function. The caller + * provides the code to execute upon delivery of the message. + * If a Peer is disconnected, all messages pending delivery + * at either end of the connection will not be delivered. + * + * When creating the Peer set, the caller needs to provide a + * Scheduler object for managing the timing and delivery + * of messages. After constructing the network, and establishing + * connections, the caller uses the scheduler's step* functions + * to drive messages through the network. + * + * The graph of peers and connections is internally represented + * using Digraph. Clients have + * const access to that graph to perform additional operations not + * directly provided by BasicNetwork. + * + * Peer Requirements: + * + * Peer should be a lightweight type, cheap to copy + * and/or move. A good candidate is a simple pointer to + * the underlying user defined type in the simulation. + * + * Expression Type Requirements + * ---------- ---- ------------ + * P Peer + * u, v Values of type P + * P u(v) CopyConstructible + * u.~P() Destructible + * u == v bool EqualityComparable + * u < v bool LessThanComparable + * std::hash

class std::hash is defined for P + * ! u bool true if u is not-a-peer + */ template class BasicNetwork { @@ -92,79 +92,84 @@ public: BasicNetwork(Scheduler& s); - /** Connect two peers. - - The link is directed, with `from` establishing - the outbound connection and `to` receiving the - incoming connection. - - Preconditions: - - from != to (self connect disallowed). - - A link between from and to does not - already exist (duplicates disallowed). - - Effects: - - Creates a link between from and to. - - @param `from` The source of the outgoing connection - @param `to` The recipient of the incoming connection - @param `delay` The time delay of all delivered messages - @return `true` if a new connection was established - */ + /** + * Connect two peers. + * + * The link is directed, with `from` establishing + * the outbound connection and `to` receiving the + * incoming connection. + * + * Preconditions: + * + * from != to (self connect disallowed). + * + * A link between from and to does not + * already exist (duplicates disallowed). + * + * Effects: + * + * Creates a link between from and to. + * + * @param `from` The source of the outgoing connection + * @param `to` The recipient of the incoming connection + * @param `delay` The time delay of all delivered messages + * @return `true` if a new connection was established + */ bool connect(Peer const& from, Peer const& to, duration const& delay = std::chrono::seconds{0}); - /** Break a link. - - Effects: - - If a connection is present, both ends are - disconnected. - - Any pending messages on the connection - are discarded. - - @return `true` if a connection was broken. - */ + /** + * Break a link. + * + * Effects: + * + * If a connection is present, both ends are + * disconnected. + * + * Any pending messages on the connection + * are discarded. + * + * @return `true` if a connection was broken. + */ bool disconnect(Peer const& peer1, Peer const& peer2); - /** Send a message to a peer. - - Preconditions: - - A link exists between from and to. - - Effects: - - If the link is not broken when the - link's `delay` time has elapsed, - the function will be invoked with - no arguments. - - @note Its the caller's responsibility to - ensure that the body of the function performs - activity consistent with `from`'s receipt of - a message from `to`. - */ + /** + * Send a message to a peer. + * + * Preconditions: + * + * A link exists between from and to. + * + * Effects: + * + * If the link is not broken when the + * link's `delay` time has elapsed, + * the function will be invoked with + * no arguments. + * + * @note Its the caller's responsibility to + * ensure that the body of the function performs + * activity consistent with `from`'s receipt of + * a message from `to`. + */ template void send(Peer const& from, Peer const& to, Function&& f); - /** Return the range of active links. - - @return A random access range over Digraph::Edge instances - */ + /** + * Return the range of active links. + * + * @return A random access range over Digraph::Edge instances + */ auto links(Peer const& from) { return links_.outEdges(from); } - /** Return the underlying digraph + /** + * Return the underlying digraph */ [[nodiscard]] Digraph const& graph() const diff --git a/src/test/csf/CollectorRef.h b/src/test/csf/CollectorRef.h index 5bc55c7515..3aef4d617f 100644 --- a/src/test/csf/CollectorRef.h +++ b/src/test/csf/CollectorRef.h @@ -12,45 +12,45 @@ namespace xrpl::test::csf { -/** Holds a type-erased reference to an arbitrary collector. - - A collector is any class that implements - - on(NodeID, SimTime, Event) - - for all events emitted by a Peer. - - This class is used to type-erase the actual collector used by each peer in - the simulation. The idea is to compose complicated and typed collectors - using the helpers in collectors.h, then only type erase at the higher-most - level when adding to the simulation. - - The example code below demonstrates the reason for storing the collector - as a reference. The collector's lifetime will generally be longer than - the simulation; perhaps several simulations are run for a single collector - instance. The collector potentially stores lots of data as well, so the - simulation needs to point to the single instance, rather than requiring - collectors to manage copying that data efficiently in their design. - - @code - // Initialize a specific collector that might write to a file. - SomeFancyCollector collector{"out.file"}; - - // Setup your simulation - Sim sim(trustgraph, topology, collector); - - // Run the simulation - sim.run(100); - - // do any reported related to the collector - collector.report(); - - @endcode - - @note If a new event type is added, it needs to be added to the interfaces - below. - -*/ +/** + * Holds a type-erased reference to an arbitrary collector. + * + * A collector is any class that implements + * + * on(NodeID, SimTime, Event) + * + * for all events emitted by a Peer. + * + * This class is used to type-erase the actual collector used by each peer in + * the simulation. The idea is to compose complicated and typed collectors + * using the helpers in collectors.h, then only type erase at the higher-most + * level when adding to the simulation. + * + * The example code below demonstrates the reason for storing the collector + * as a reference. The collector's lifetime will generally be longer than + * the simulation; perhaps several simulations are run for a single collector + * instance. The collector potentially stores lots of data as well, so the + * simulation needs to point to the single instance, rather than requiring + * collectors to manage copying that data efficiently in their design. + * + * @code + * // Initialize a specific collector that might write to a file. + * SomeFancyCollector collector{"out.file"}; + * + * // Setup your simulation + * Sim sim(trustgraph, topology, collector); + * + * // Run the simulation + * sim.run(100); + * + * // do any reported related to the collector + * collector.report(); + * + * @endcode + * + * @note If a new event type is added, it needs to be added to the interfaces + * below. + */ class CollectorRef { using tp = SimTime; @@ -296,16 +296,17 @@ public: } }; -/** A container of CollectorRefs - - A set of CollectorRef instances that process the same events. An event is - processed by collectors in the order the collectors were added. - - This class type-erases the collector instances. By contract, the - Collectors/collectors class/helper in collectors.h are not type erased and - offer an opportunity for type transformations and combinations with - improved compiler optimizations. -*/ +/** + * A container of CollectorRefs + * + * A set of CollectorRef instances that process the same events. An event is + * processed by collectors in the order the collectors were added. + * + * This class type-erases the collector instances. By contract, the + * Collectors/collectors class/helper in collectors.h are not type erased and + * offer an opportunity for type transformations and combinations with + * improved compiler optimizations. + */ class CollectorRefs { std::vector collectors_; diff --git a/src/test/csf/Digraph.h b/src/test/csf/Digraph.h index 82ed713561..b1b49404b5 100644 --- a/src/test/csf/Digraph.h +++ b/src/test/csf/Digraph.h @@ -21,16 +21,16 @@ struct NoEdgeData namespace test::csf { -/** Directed graph - -Basic directed graph that uses an adjacency list to represent out edges. - -Instances of Vertex uniquely identify vertices in the graph. Instances of -EdgeData is any data to store in the edge connecting two vertices. - -Both Vertex and EdgeData should be lightweight and cheap to copy. - -*/ +/** + * Directed graph + * + * Basic directed graph that uses an adjacency list to represent out edges. + * + * Instances of Vertex uniquely identify vertices in the graph. Instances of + * EdgeData is any data to store in the edge connecting two vertices. + * + * Both Vertex and EdgeData should be lightweight and cheap to copy. + */ template class Digraph { @@ -42,41 +42,42 @@ class Digraph Links empty_; public: - /** Connect two vertices - - @param source The source vertex - @param target The target vertex - @param e The edge data - @return true if the edge was created - - */ + /** + * Connect two vertices + * + * @param source The source vertex + * @param target The target vertex + * @param e The edge data + * @return true if the edge was created + */ bool connect(Vertex source, Vertex target, EdgeData e) { return graph_[source].emplace(target, e).second; } - /** Connect two vertices using default constructed edge data - - @param source The source vertex - @param target The target vertex - @return true if the edge was created - - */ + /** + * Connect two vertices using default constructed edge data + * + * @param source The source vertex + * @param target The target vertex + * @return true if the edge was created + */ bool connect(Vertex source, Vertex target) { return connect(source, target, EdgeData{}); } - /** Disconnect two vertices - - @param source The source vertex - @param target The target vertex - @return true if an edge was removed - - If source is not connected to target, this function does nothing. - */ + /** + * Disconnect two vertices + * + * @param source The source vertex + * @param target The target vertex + * @return true if an edge was removed + * + * If source is not connected to target, this function does nothing. + */ bool disconnect(Vertex source, Vertex target) { @@ -88,13 +89,13 @@ public: return false; } - /** Return edge data between two vertices - - @param source The source vertex - @param target The target vertex - @return optional which is std::nullopt if no edge exists - - */ + /** + * Return edge data between two vertices + * + * @param source The source vertex + * @param target The target vertex + * @return optional which is std::nullopt if no edge exists + */ [[nodiscard]] std::optional edge(Vertex source, Vertex target) const { @@ -108,23 +109,25 @@ public: return std::nullopt; } - /** Check if two vertices are connected - - @param source The source vertex - @param target The target vertex - @return true if the source has an out edge to target - */ + /** + * Check if two vertices are connected + * + * @param source The source vertex + * @param target The target vertex + * @return true if the source has an out edge to target + */ [[nodiscard]] bool connected(Vertex source, Vertex target) const { return edge(source, target) != std::nullopt; } - /** Range over vertices in the graph - - @return A boost transformed range over the vertices with out edges in - the graph - */ + /** + * Range over vertices in the graph + * + * @return A boost transformed range over the vertices with out edges in + * the graph + */ [[nodiscard]] auto outVertices() const { @@ -132,10 +135,11 @@ public: graph_, [](Graph::value_type const& v) { return v.first; }); } - /** Range over target vertices - - @param source The source vertex - @return A boost transformed range over the target vertices of source. + /** + * Range over target vertices + * + * @param source The source vertex + * @return A boost transformed range over the target vertices of source. */ [[nodiscard]] auto outVertices(Vertex source) const @@ -148,7 +152,8 @@ public: return boost::adaptors::transform(empty_, transform); } - /** Vertices and data associated with an Edge + /** + * Vertices and data associated with an Edge */ struct Edge { @@ -157,12 +162,13 @@ public: EdgeData data; }; - /** Range of out edges - - @param source The source vertex - @return A boost transformed range of Edge type for all out edges of - source. - */ + /** + * Range of out edges + * + * @param source The source vertex + * @return A boost transformed range of Edge type for all out edges of + * source. + */ [[nodiscard]] auto outEdges(Vertex source) const { @@ -177,11 +183,12 @@ public: return boost::adaptors::transform(empty_, transform); } - /** Vertex out-degree - - @param source The source vertex - @return The number of outgoing edges from source - */ + /** + * Vertex out-degree + * + * @param source The source vertex + * @return The number of outgoing edges from source + */ [[nodiscard]] std::size_t outDegree(Vertex source) const { @@ -191,14 +198,15 @@ public: return 0; } - /** Save GraphViz dot file - - Save a GraphViz dot description of the graph - @param fileName The output file (creates) - @param vertexName A invocable T vertexName(Vertex const &) that - returns the name target use for the vertex in the file - T must be ostream-able - */ + /** + * Save GraphViz dot file + * + * Save a GraphViz dot description of the graph + * @param fileName The output file (creates) + * @param vertexName A invocable T vertexName(Vertex const &) that + * returns the name target use for the vertex in the file + * T must be ostream-able + */ template void saveDot(std::ostream& out, VertexName&& vertexName) const diff --git a/src/test/csf/Histogram.h b/src/test/csf/Histogram.h index 60cb9a132e..62791c10f3 100644 --- a/src/test/csf/Histogram.h +++ b/src/test/csf/Histogram.h @@ -8,17 +8,16 @@ namespace xrpl::test::csf { -/** Basic histogram. - - Histogram for a type `T` that satisfies - - Default construction: T{} - - Comparison : T a, b; bool res = a < b - - Addition: T a, b; T c = a + b; - - Multiplication : T a, std::size_t b; T c = a * b; - - Division: T a; std::size_t b; T c = a/b; - - -*/ +/** + * Basic histogram. + * + * Histogram for a type `T` that satisfies + * - Default construction: T{} + * - Comparison : T a, b; bool res = a < b + * - Addition: T a, b; T c = a + b; + * - Multiplication : T a, std::size_t b; T c = a * b; + * - Division: T a; std::size_t b; T c = a/b; + */ template > class Histogram { @@ -28,7 +27,9 @@ class Histogram std::size_t samples_ = 0; public: - /** Insert an sample */ + /** + * Insert an sample + */ void insert(T const& s) { @@ -36,35 +37,45 @@ public: ++samples_; } - /** The number of samples */ + /** + * The number of samples + */ [[nodiscard]] std::size_t size() const { return samples_; } - /** The number of distinct samples (bins) */ + /** + * The number of distinct samples (bins) + */ [[nodiscard]] std::size_t numBins() const { return counts_.size(); } - /** Minimum observed value */ + /** + * Minimum observed value + */ [[nodiscard]] T minValue() const { return counts_.empty() ? T{} : counts_.begin()->first; } - /** Maximum observed value */ + /** + * Maximum observed value + */ [[nodiscard]] T maxValue() const { return counts_.empty() ? T{} : counts_.rbegin()->first; } - /** Histogram average */ + /** + * Histogram average + */ [[nodiscard]] T avg() const { @@ -80,12 +91,13 @@ public: return tmp / samples_; } - /** Calculate the given percentile of the distribution. - - @param p Percentile between 0 and 1, e.g. 0.50 is 50-th percentile - If the percentile falls between two bins, uses the nearest bin. - @return The given percentile of the distribution - */ + /** + * Calculate the given percentile of the distribution. + * + * @param p Percentile between 0 and 1, e.g. 0.50 is 50-th percentile + * If the percentile falls between two bins, uses the nearest bin. + * @return The given percentile of the distribution + */ [[nodiscard]] T percentile(float p) const { diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index 9d29704172..53b26d05cf 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -42,24 +42,26 @@ namespace xrpl::test::csf { namespace bc = boost::container; -/** A single peer in the simulation. - - This is the main work-horse of the consensus simulation framework and is - where many other components are integrated. The peer - - - Implements the Callbacks required by Consensus - - Manages trust & network connections with other peers - - Issues events back to the simulation based on its actions for analysis - by Collectors - - Exposes most internal state for forcibly simulating arbitrary scenarios -*/ +/** + * A single peer in the simulation. + * + * This is the main work-horse of the consensus simulation framework and is + * where many other components are integrated. The peer + * + * - Implements the Callbacks required by Consensus + * - Manages trust & network connections with other peers + * - Issues events back to the simulation based on its actions for analysis + * by Collectors + * - Exposes most internal state for forcibly simulating arbitrary scenarios + */ struct Peer { - /** Basic wrapper of a proposed position taken by a peer. - - For real consensus, this would add additional data for serialization - and signing. For simulation, nothing extra is needed. - */ + /** + * Basic wrapper of a proposed position taken by a peer. + * + * For real consensus, this would add additional data for serialization + * and signing. For simulation, nothing extra is needed. + */ class Position { public: @@ -89,16 +91,21 @@ struct Peer Proposal proposal_; }; - /** Simulated delays in internal peer processing. + /** + * Simulated delays in internal peer processing. */ struct ProcessingDelays { - //! Delay in consensus calling doAccept to accepting and issuing - //! validation - //! TODO: This should be a function of the number of transactions + /** + * Delay in consensus calling doAccept to accepting and issuing + * validation + * TODO: This should be a function of the number of transactions + */ std::chrono::milliseconds ledgerAccept{0}; - //! Delay in processing validations from remote peers + /** + * Delay in processing validations from remote peers + */ std::chrono::milliseconds recvValidation{0}; // Return the receive delay for message type M, default is no delay @@ -122,7 +129,8 @@ struct Peer { }; - /** Generic Validations adaptor that simply ignores recently stale + /** + * Generic Validations adaptor that simply ignores recently stale * validations */ class ValAdaptor @@ -165,7 +173,9 @@ struct Peer } }; - //! Type definitions for generic consensus + /** + * Type definitions for generic consensus + */ using Ledger_t = Ledger; using NodeID_t = PeerID; using NodeKey_t = PeerKey; @@ -174,74 +184,114 @@ struct Peer using Result = ConsensusResult; using NodeKey = Validation::NodeKey; - //! Logging support that prefixes messages with the peer ID + /** + * Logging support that prefixes messages with the peer ID + */ beast::WrappedSink sink; beast::Journal j; - //! Generic consensus + /** + * Generic consensus + */ Consensus consensus; - //! Our unique ID + /** + * Our unique ID + */ PeerID id; - //! Current signing key + /** + * Current signing key + */ PeerKey key; - //! The oracle that manages unique ledgers + /** + * The oracle that manages unique ledgers + */ LedgerOracle& oracle; - //! Scheduler of events + /** + * Scheduler of events + */ Scheduler& scheduler; - //! Handle to network for sending messages + /** + * Handle to network for sending messages + */ BasicNetwork& net; - //! Handle to Trust graph of network + /** + * Handle to Trust graph of network + */ TrustGraph& trustGraph; - //! openTxs that haven't been closed in a ledger yet + /** + * openTxs that haven't been closed in a ledger yet + */ TxSetType openTxs; - //! The last ledger closed by this node + /** + * The last ledger closed by this node + */ Ledger lastClosedLedger; - //! Ledgers this node has closed or loaded from the network + /** + * Ledgers this node has closed or loaded from the network + */ hash_map ledgers; - //! Validations from trusted nodes + /** + * Validations from trusted nodes + */ Validations validations; - //! The most recent ledger that has been fully validated by the network from - //! the perspective of this Peer + /** + * The most recent ledger that has been fully validated by the network from + * the perspective of this Peer + */ Ledger fullyValidatedLedger; //------------------------------------------------------------------------- // Store most network messages; these could be purged if memory use ever // becomes problematic - //! Map from Ledger::ID to vector of Positions with that ledger - //! as the prior ledger + /** + * Map from Ledger::ID to vector of Positions with that ledger + * as the prior ledger + */ bc::flat_map> peerPositions; - //! TxSet associated with a TxSet::ID + /** + * TxSet associated with a TxSet::ID + */ bc::flat_map txSets; // Ledgers/TxSets we are acquiring and when that request times out bc::flat_map acquiringLedgers; bc::flat_map acquiringTxSets; - //! The number of ledgers this peer has completed + /** + * The number of ledgers this peer has completed + */ int completedLedgers = 0; - //! The number of ledgers this peer should complete before stopping to run + /** + * The number of ledgers this peer should complete before stopping to run + */ int targetLedgers = std::numeric_limits::max(); - //! Skew of time relative to the common scheduler clock + /** + * Skew of time relative to the common scheduler clock + */ std::chrono::seconds clockSkew{0}; - //! Simulated delays to use for internal processing + /** + * Simulated delays to use for internal processing + */ ProcessingDelays delays; - //! Whether to simulate running as validator or a tracking node + /** + * Whether to simulate running as validator or a tracking node + */ bool runAsValidator = true; // TODO: Consider removing these two, they are only a convenience for tests @@ -259,20 +309,22 @@ struct Peer // Simulation parameters ConsensusParms consensusParms; - //! The collectors to report events to + /** + * The collectors to report events to + */ CollectorRefs& collectors; - /** Constructor - - @param i Unique PeerID - @param s Simulation Scheduler - @param o Simulation Oracle - @param n Simulation network - @param tg Simulation trust graph - @param c Simulation collectors - @param jIn Simulation journal - - */ + /** + * Constructor + * + * @param i Unique PeerID + * @param s Simulation Scheduler + * @param o Simulation Oracle + * @param n Simulation network + * @param tg Simulation trust graph + * @param c Simulation collectors + * @param jIn Simulation journal + */ Peer( PeerID i, Scheduler& s, @@ -302,9 +354,10 @@ struct Peer trustGraph.trust(this, this); } - /** Schedule the provided callback in `when` duration, but if - `when` is 0, call immediately - */ + /** + * Schedule the provided callback in `when` duration, but if + * `when` is 0, call immediately + */ template void schedule(std::chrono::nanoseconds when, T&& what) @@ -368,14 +421,15 @@ struct Peer return false; } - /** Create network connection - - Creates a new outbound connection to another Peer if none exists - - @param o The peer with the inbound connection - @param dur The fixed delay for messages between the two Peers - @return Whether the connection was created. - */ + /** + * Create network connection + * + * Creates a new outbound connection to another Peer if none exists + * + * @param o The peer with the inbound connection + * @param dur The fixed delay for messages between the two Peers + * @return Whether the connection was created. + */ bool connect(Peer& o, SimDuration dur) @@ -383,13 +437,14 @@ struct Peer return net.connect(this, &o, dur); } - /** Remove a network connection - - Removes a connection between peers if one exists - - @param o The peer we disconnect from - @return Whether the connection was removed - */ + /** + * Remove a network connection + * + * Removes a connection between peers if one exists + * + * @param o The peer we disconnect from + * @return Whether the connection was removed + */ bool disconnect(Peer& o) { @@ -653,7 +708,9 @@ struct Peer //-------------------------------------------------------------------------- // Validation members - /** Add a trusted validation and return true if it is worth forwarding */ + /** + * Add a trusted validation and return true if it is worth forwarding + */ bool addTrustedValidation(Validation v) { @@ -670,7 +727,9 @@ struct Peer return true; } - /** Check if a new ledger can be deemed fully validated */ + /** + * Check if a new ledger can be deemed fully validated + */ void checkFullyValidated(Ledger const& ledger) { @@ -872,7 +931,9 @@ struct Peer //-------------------------------------------------------------------------- // Simulation "driver" members - //! Heartbeat timer call + /** + * Heartbeat timer call + */ void timerEntry() { @@ -939,16 +1000,17 @@ struct Peer // TODO: Make this more robust hash_map txInjections; - /** Inject non-consensus Tx - - Injects a transactionsinto the ledger following prevLedger's sequence - number. - - @param prevLedger The ledger we are building the new ledger on top of - @param src The Consensus TxSet - @return Consensus TxSet with inject transactions added if prevLedger.seq - matches a previously registered Tx. - */ + /** + * Inject non-consensus Tx + * + * Injects a transactionsinto the ledger following prevLedger's sequence + * number. + * + * @param prevLedger The ledger we are building the new ledger on top of + * @param src The Consensus TxSet + * @return Consensus TxSet with inject transactions added if prevLedger.seq + * matches a previously registered Tx. + */ TxSet injectTxs(Ledger prevLedger, TxSet const& src) { diff --git a/src/test/csf/PeerGroup.h b/src/test/csf/PeerGroup.h index e5efecac34..1c31209ef3 100644 --- a/src/test/csf/PeerGroup.h +++ b/src/test/csf/PeerGroup.h @@ -17,14 +17,15 @@ namespace xrpl::test::csf { -/** A group of simulation Peers - - A PeerGroup is a convenient handle for logically grouping peers together, - and then creating trust or network relations for the group at large. Peer - groups may also be combined to build out more complex structures. - - The PeerGroup provides random access style iterators and operator[] -*/ +/** + * A group of simulation Peers + * + * A PeerGroup is a convenient handle for logically grouping peers together, + * and then creating trust or network relations for the group at large. Peer + * groups may also be combined to build out more complex structures. + * + * The PeerGroup provides random access style iterators and operator[] + */ class PeerGroup { using peers_type = std::vector; @@ -102,12 +103,13 @@ public: return peers_.size(); } - /** Establish trust - - Establish trust from all peers in this group to all peers in o - - @param o The group of peers to trust - */ + /** + * Establish trust + * + * Establish trust from all peers in this group to all peers in o + * + * @param o The group of peers to trust + */ void trust(PeerGroup const& o) { @@ -120,12 +122,13 @@ public: } } - /** Revoke trust - - Revoke trust from all peers in this group to all peers in o - - @param o The group of peers to untrust - */ + /** + * Revoke trust + * + * Revoke trust from all peers in this group to all peers in o + * + * @param o The group of peers to untrust + */ void untrust(PeerGroup const& o) { @@ -138,16 +141,15 @@ public: } } - /** Establish network connection - - Establish outbound connections from all peers in this group to all peers - in o. If a connection already exists, no new connection is established. - - @param o The group of peers to connect to (will get inbound connections) - @param delay The fixed messaging delay for all established connections - - - */ + /** + * Establish network connection + * + * Establish outbound connections from all peers in this group to all peers + * in o. If a connection already exists, no new connection is established. + * + * @param o The group of peers to connect to (will get inbound connections) + * @param delay The fixed messaging delay for all established connections + */ void connect(PeerGroup const& o, SimDuration delay) { @@ -162,12 +164,13 @@ public: } } - /** Destroy network connection - - Destroy connections from all peers in this group to all peers in o - - @param o The group of peers to disconnect from - */ + /** + * Destroy network connection + * + * Destroy connections from all peers in this group to all peers in o + * + * @param o The group of peers to disconnect from + */ void disconnect(PeerGroup const& o) { @@ -180,14 +183,15 @@ public: } } - /** Establish trust and network connection - - Establish trust and create a network connection with fixed delay - from all peers in this group to all peers in o - - @param o The group of peers to trust and connect to - @param delay The fixed messaging delay for all established connections - */ + /** + * Establish trust and network connection + * + * Establish trust and create a network connection with fixed delay + * from all peers in this group to all peers in o + * + * @param o The group of peers to trust and connect to + * @param delay The fixed messaging delay for all established connections + */ void trustAndConnect(PeerGroup const& o, SimDuration delay) { @@ -195,15 +199,15 @@ public: connect(o, delay); } - /** Establish network connections based on trust relations - - For each peers in this group, create outbound network connection - to the set of peers it trusts. If a connection already exists, it is - not recreated. - - @param delay The fixed messaging delay for all established connections - - */ + /** + * Establish network connections based on trust relations + * + * For each peers in this group, create outbound network connection + * to the set of peers it trusts. If a connection already exists, it is + * not recreated. + * + * @param delay The fixed messaging delay for all established connections + */ void connectFromTrust(SimDuration delay) { @@ -253,25 +257,25 @@ public: } }; -/** Randomly generate peer groups according to ranks. - - Generates random peer groups based on a provided ranking of peers. This - mimics a process of randomly generating UNLs, where more "important" peers - are more likely to appear in a UNL. - - `numGroups` subgroups are generated by randomly sampling without without - replacement from peers according to the `ranks`. - - - - @param peers The group of peers - @param ranks The relative importance of each peer, must match the size of - peers. Higher relative rank means more likely to be sampled. - @param numGroups The number of peer link groups to generate - @param sizeDist The distribution that determines the size of a link group - @param g The uniform random bit generator - -*/ +/** + * Randomly generate peer groups according to ranks. + * + * Generates random peer groups based on a provided ranking of peers. This + * mimics a process of randomly generating UNLs, where more "important" peers + * are more likely to appear in a UNL. + * + * `numGroups` subgroups are generated by randomly sampling without without + * replacement from peers according to the `ranks`. + * + * + * + * @param peers The group of peers + * @param ranks The relative importance of each peer, must match the size of + * peers. Higher relative rank means more likely to be sampled. + * @param numGroups The number of peer link groups to generate + * @param sizeDist The distribution that determines the size of a link group + * @param g The uniform random bit generator + */ template std::vector randomRankedGroups( @@ -295,10 +299,11 @@ randomRankedGroups( return groups; } -/** Generate random trust groups based on peer rankings. - - @see randomRankedGroups for descriptions of the arguments -*/ +/** + * Generate random trust groups based on peer rankings. + * + * @see randomRankedGroups for descriptions of the arguments + */ template void randomRankedTrust( @@ -318,10 +323,11 @@ randomRankedTrust( } } -/** Generate random network groups based on peer rankings. - - @see randomRankedGroups for descriptions of the arguments -*/ +/** + * Generate random network groups based on peer rankings. + * + * @see randomRankedGroups for descriptions of the arguments + */ template void randomRankedConnect( diff --git a/src/test/csf/Proposal.h b/src/test/csf/Proposal.h index 06486daed3..ecf430ae8d 100644 --- a/src/test/csf/Proposal.h +++ b/src/test/csf/Proposal.h @@ -7,9 +7,10 @@ #include namespace xrpl::test::csf { -/** Proposal is a position taken in the consensus process and is represented - directly from the generic types. -*/ +/** + * Proposal is a position taken in the consensus process and is represented + * directly from the generic types. + */ using Proposal = ConsensusProposal; } // namespace xrpl::test::csf diff --git a/src/test/csf/Scheduler.h b/src/test/csf/Scheduler.h index 3fc187e1e7..b1ac2bb5d4 100644 --- a/src/test/csf/Scheduler.h +++ b/src/test/csf/Scheduler.h @@ -12,17 +12,18 @@ namespace xrpl::test::csf { -/** Simulated discrete-event scheduler. - - Simulates the behavior of events using a single common clock. - - An event is modeled using a lambda function and is scheduled to occur at a - specific time. Events may be canceled using a token returned when the - event is scheduled. - - The caller uses one or more of the step, stepOne, stepFor, stepUntil and - stepWhile functions to process scheduled events. -*/ +/** + * Simulated discrete-event scheduler. + * + * Simulates the behavior of events using a single common clock. + * + * An event is modeled using a lambda function and is scheduled to occur at a + * specific time. Events may be canceled using a token returned when the + * event is scheduled. + * + * The caller uses one or more of the step, stepOne, stepFor, stepUntil and + * stepWhile functions to process scheduled events. + */ class Scheduler { public: @@ -135,116 +136,127 @@ public: Scheduler(); - /** Return the clock. (aged_containers want a non-const ref =( */ + /** + * Return the clock. (aged_containers want a non-const ref =( + */ clock_type& clock() const; - /** Return the current network time. - - @note The epoch is unspecified - */ + /** + * Return the current network time. + * + * @note The epoch is unspecified + */ time_point now() const; // Used to cancel timers struct CancelToken; - /** Schedule an event at a specific time - - Effects: - - When the network time is reached, - the function will be called with - no arguments. - */ + /** + * Schedule an event at a specific time + * + * Effects: + * + * When the network time is reached, + * the function will be called with + * no arguments. + */ template CancelToken at(time_point const& when, Function&& f); - /** Schedule an event after a specified duration passes - - Effects: - - When the specified time has elapsed, - the function will be called with - no arguments. - */ + /** + * Schedule an event after a specified duration passes + * + * Effects: + * + * When the specified time has elapsed, + * the function will be called with + * no arguments. + */ template CancelToken in(duration const& delay, Function&& f); - /** Cancel a timer. - - Preconditions: - - `token` was the return value of a call - timer() which has not yet been invoked. - */ + /** + * Cancel a timer. + * + * Preconditions: + * + * `token` was the return value of a call + * timer() which has not yet been invoked. + */ void cancel(CancelToken const& token); - /** Run the scheduler for up to one event. - - Effects: - - The clock is advanced to the time - of the last delivered event. - - @return `true` if an event was processed. - */ + /** + * Run the scheduler for up to one event. + * + * Effects: + * + * The clock is advanced to the time + * of the last delivered event. + * + * @return `true` if an event was processed. + */ bool stepOne(); - /** Run the scheduler until no events remain. - - Effects: - - The clock is advanced to the time - of the last event. - - @return `true` if an event was processed. - */ + /** + * Run the scheduler until no events remain. + * + * Effects: + * + * The clock is advanced to the time + * of the last event. + * + * @return `true` if an event was processed. + */ bool step(); - /** Run the scheduler while a condition is true. - - Function takes no arguments and will be called - repeatedly after each event is processed to - decide whether to continue. - - Effects: - - The clock is advanced to the time - of the last delivered event. - - @return `true` if any event was processed. - */ + /** + * Run the scheduler while a condition is true. + * + * Function takes no arguments and will be called + * repeatedly after each event is processed to + * decide whether to continue. + * + * Effects: + * + * The clock is advanced to the time + * of the last delivered event. + * + * @return `true` if any event was processed. + */ template bool stepWhile(Function&& func); - /** Run the scheduler until the specified time. - - Effects: - - The clock is advanced to the - specified time. - - @return `true` if any event remain. - */ + /** + * Run the scheduler until the specified time. + * + * Effects: + * + * The clock is advanced to the + * specified time. + * + * @return `true` if any event remain. + */ bool stepUntil(time_point const& until); - /** Run the scheduler until time has elapsed. - - Effects: - - The clock is advanced by the - specified duration. - - @return `true` if any event remain. - */ + /** + * Run the scheduler until time has elapsed. + * + * Effects: + * + * The clock is advanced by the + * specified duration. + * + * @return `true` if any event remain. + */ template bool stepFor(std::chrono::duration const& amount); diff --git a/src/test/csf/Sim.h b/src/test/csf/Sim.h index d2a63f6507..94d26d5e06 100644 --- a/src/test/csf/Sim.h +++ b/src/test/csf/Sim.h @@ -21,7 +21,9 @@ namespace xrpl::test::csf { -/** Sink that prepends simulation time to messages */ +/** + * Sink that prepends simulation time to messages + */ class BasicSink : public beast::Journal::Sink { Scheduler::clock_type const& clock_; @@ -65,28 +67,29 @@ public: TrustGraph trustGraph; CollectorRefs collectors; - /** Create a simulation - - Creates a new simulation. The simulation has no peers, no trust links - and no network connections. - - */ + /** + * Create a simulation + * + * Creates a new simulation. The simulation has no peers, no trust links + * and no network connections. + */ // NOLINTNEXTLINE(bugprone-random-generator-seed): fixed seed for reproducible test Sim() : sink{scheduler.clock()}, j{sink}, net{scheduler} { } - /** Create a new group of peers. - - Creates a new group of peers. The peers do not have any trust relations - or network connections by default. Those must be configured by the - client. - - @param numPeers The number of peers in the group - @return PeerGroup representing these new peers - - @note This increases the number of peers in the simulation by numPeers. - */ + /** + * Create a new group of peers. + * + * Creates a new group of peers. The peers do not have any trust relations + * or network connections by default. Those must be configured by the + * client. + * + * @param numPeers The number of peers in the group + * @return PeerGroup representing these new peers + * + * @note This increases the number of peers in the simulation by numPeers. + */ PeerGroup createGroup(std::size_t numPeers) { @@ -109,48 +112,57 @@ public: return res; } - //! The number of peers in the simulation + /** + * The number of peers in the simulation + */ std::size_t size() const { return peers_.size(); } - /** Run consensus protocol to generate the provided number of ledgers. - - Has each peer run consensus until it closes `ledgers` more ledgers. - - @param ledgers The number of additional ledgers to close - */ + /** + * Run consensus protocol to generate the provided number of ledgers. + * + * Has each peer run consensus until it closes `ledgers` more ledgers. + * + * @param ledgers The number of additional ledgers to close + */ void run(int ledgers); - /** Run consensus for the given duration */ + /** + * Run consensus for the given duration + */ void run(SimDuration const& dur); - /** Check whether all peers in the group are synchronized. - - Nodes in the group are synchronized if they share the same last - fully validated and last generated ledger. - */ + /** + * Check whether all peers in the group are synchronized. + * + * Nodes in the group are synchronized if they share the same last + * fully validated and last generated ledger. + */ static bool synchronized(PeerGroup const& g); - /** Check whether all peers in the network are synchronized + /** + * Check whether all peers in the network are synchronized */ bool synchronized() const; - /** Calculate the number of branches in the group. - - A branch occurs if two nodes in the group have fullyValidatedLedgers - that are not on the same chain of ledgers. - */ + /** + * Calculate the number of branches in the group. + * + * A branch occurs if two nodes in the group have fullyValidatedLedgers + * that are not on the same chain of ledgers. + */ std::size_t branches(PeerGroup const& g) const; - /** Calculate the number of branches in the network + /** + * Calculate the number of branches in the network */ std::size_t branches() const; diff --git a/src/test/csf/TrustGraph.h b/src/test/csf/TrustGraph.h index a10e318706..d46a887364 100644 --- a/src/test/csf/TrustGraph.h +++ b/src/test/csf/TrustGraph.h @@ -10,13 +10,14 @@ namespace xrpl::test::csf { -/** Trust graph - - Trust is a directed relationship from a node i to node j. - If node i trusts node j, then node i has node j in its UNL. - This class wraps a digraph representing the trust relationships for all - peers in the simulation. -*/ +/** + * Trust graph + * + * Trust is a directed relationship from a node i to node j. + * If node i trusts node j, then node i has node j in its UNL. + * This class wraps a digraph representing the trust relationships for all + * peers in the simulation. + */ template class TrustGraph { @@ -25,7 +26,8 @@ class TrustGraph Graph graph_; public: - /** Create an empty trust graph + /** + * Create an empty trust graph */ TrustGraph() = default; @@ -35,29 +37,30 @@ public: return graph_; } - /** Create trust - - Establish trust between Peer `from` and Peer `to`; as if `from` put `to` - in its UNL. - - @param from The peer granting trust - @param to The peer receiving trust - - */ + /** + * Create trust + * + * Establish trust between Peer `from` and Peer `to`; as if `from` put `to` + * in its UNL. + * + * @param from The peer granting trust + * @param to The peer receiving trust + */ void trust(Peer const& from, Peer const& to) { graph_.connect(from, to); } - /** Remove trust - - Revoke trust from Peer `from` to Peer `to`; as if `from` removed `to` - from its UNL. - - @param from The peer revoking trust - @param to The peer being revoked - */ + /** + * Remove trust + * + * Revoke trust from Peer `from` to Peer `to`; as if `from` removed `to` + * from its UNL. + * + * @param from The peer revoking trust + * @param to The peer being revoked + */ void untrust(Peer const& from, Peer const& to) { @@ -71,19 +74,21 @@ public: return graph_.connected(from, to); } - /** Range over trusted peers - - @param a The node granting trust - @return boost transformed range over nodes `a` trusts, i.e. the nodes - in its UNL - */ + /** + * Range over trusted peers + * + * @param a The node granting trust + * @return boost transformed range over nodes `a` trusts, i.e. the nodes + * in its UNL + */ [[nodiscard]] auto trustedPeers(Peer const& a) const { return graph_.outVertices(a); } - /** An example of nodes that fail the whitepaper no-forking condition + /** + * An example of nodes that fail the whitepaper no-forking condition */ struct ForkInfo { @@ -133,9 +138,10 @@ public: return res; } - /** Check whether this trust graph satisfies the whitepaper no-forking - condition - */ + /** + * Check whether this trust graph satisfies the whitepaper no-forking + * condition + */ [[nodiscard]] bool canFork(double quorum) const { diff --git a/src/test/csf/Tx.h b/src/test/csf/Tx.h index 75254f865a..0412d346f3 100644 --- a/src/test/csf/Tx.h +++ b/src/test/csf/Tx.h @@ -17,7 +17,9 @@ namespace xrpl::test::csf { -//! A single transaction +/** + * A single transaction + */ class Tx { public: @@ -56,11 +58,15 @@ private: ID id_; }; -//!------------------------------------------------------------------------- -//! All sets of Tx are represented as a flat_set for performance. +/** + * ------------------------------------------------------------------------- + * All sets of Tx are represented as a flat_set for performance. + */ using TxSetType = boost::container::flat_set; -//! TxSet is a set of transactions to consider including in the ledger +/** + * TxSet is a set of transactions to consider including in the ledger + */ class TxSet { public: @@ -135,10 +141,11 @@ public: return id_; } - /** @return Map of Tx::ID that are missing. True means - it was in this set and not other. False means - it was in the other set and not this - */ + /** + * @return Map of Tx::ID that are missing. True means + * it was in this set and not other. False means + * it was in the other set and not this + */ [[nodiscard]] std::map compare(TxSet const& other) const { @@ -160,10 +167,14 @@ public: } private: - //! The set contains the actual transactions + /** + * The set contains the actual transactions + */ TxSetType txs_; - //! The unique ID of this tx set + /** + * The unique ID of this tx set + */ ID id_{}; }; diff --git a/src/test/csf/Validation.h b/src/test/csf/Validation.h index 73b6784534..0b9fc94890 100644 --- a/src/test/csf/Validation.h +++ b/src/test/csf/Validation.h @@ -16,15 +16,17 @@ struct PeerIDTag; //< Uniquely identifies a peer using PeerID = TaggedInteger; -/** The current key of a peer - - Eventually, the second entry in the pair can be used to model ephemeral - keys. Right now, the convention is to have the second entry 0 as the - master key. -*/ +/** + * The current key of a peer + * + * Eventually, the second entry in the pair can be used to model ephemeral + * keys. Right now, the convention is to have the second entry 0 as the + * master key. + */ using PeerKey = std::pair; -/** Validation of a specific ledger by a specific Peer. +/** + * Validation of a specific ledger by a specific Peer. */ class Validation { diff --git a/src/test/csf/collectors.h b/src/test/csf/collectors.h index 1100f6c690..f85854e5dd 100644 --- a/src/test/csf/collectors.h +++ b/src/test/csf/collectors.h @@ -32,12 +32,13 @@ namespace xrpl::test::csf { // This file contains helper functions for composing different collectors // and also defines several standard collectors available for simulations. -/** Group of collectors. - - Presents a group of collectors as a single collector which process an event - by calling each collector sequentially. This is analogous to CollectorRefs - in CollectorRef.h, but does *not* erase the type information of the combined - collectors. +/** + * Group of collectors. + * + * Presents a group of collectors as a single collector which process an event + * by calling each collector sequentially. This is analogous to CollectorRefs + * in CollectorRef.h, but does *not* erase the type information of the combined + * collectors. */ template class Collectors @@ -59,10 +60,11 @@ class Collectors } public: - /** Constructor - - @param cs References to the collectors to call together - */ + /** + * Constructor + * + * @param cs References to the collectors to call together + */ Collectors(Cs&... cs) : cs_(std::tie(cs...)) { } @@ -75,7 +77,9 @@ public: } }; -/** Create an instance of Collectors */ +/** + * Create an instance of Collectors + */ template Collectors makeCollectors(Cs&... cs) @@ -83,14 +87,15 @@ makeCollectors(Cs&... cs) return Collectors(cs...); } -/** Maintain an instance of a Collector per peer - - For each peer that emits events, this class maintains a corresponding - instance of CollectorType, only forwarding events emitted by the peer to - the related instance. - - CollectorType should be default constructible. -*/ +/** + * Maintain an instance of a Collector per peer + * + * For each peer that emits events, this class maintains a corresponding + * instance of CollectorType, only forwarding events emitted by the peer to + * the related instance. + * + * CollectorType should be default constructible. + */ template struct CollectByNode { @@ -115,7 +120,9 @@ struct CollectByNode } }; -/** Collector which ignores all events */ +/** + * Collector which ignores all events + */ struct NullCollector { template @@ -125,7 +132,9 @@ struct NullCollector } }; -/** Tracks the overall duration of a simulation */ +/** + * Tracks the overall duration of a simulation + */ struct SimDurationCollector { bool init = false; @@ -148,15 +157,16 @@ struct SimDurationCollector } }; -/** Tracks the submission -> accepted -> validated evolution of transactions. - - This collector tracks transactions through the network by monitoring the - *first* time the transaction is seen by any node in the network, or - seen by any node's accepted or fully validated ledger. - - If transactions submitted to the network do not have unique IDs, this - collector will not track subsequent submissions. -*/ +/** + * Tracks the submission -> accepted -> validated evolution of transactions. + * + * This collector tracks transactions through the network by monitoring the + * *first* time the transaction is seen by any node in the network, or + * seen by any node's accepted or fully validated ledger. + * + * If transactions submitted to the network do not have unique IDs, this + * collector will not track subsequent submissions. + */ struct TxCollector { // Counts @@ -381,12 +391,12 @@ struct TxCollector } }; -/** Tracks the accepted -> validated evolution of ledgers. - - This collector tracks ledgers through the network by monitoring the - *first* time the ledger is accepted or fully validated by ANY node. - -*/ +/** + * Tracks the accepted -> validated evolution of ledgers. + * + * This collector tracks ledgers through the network by monitoring the + * *first* time the ledger is accepted or fully validated by ANY node. + */ struct LedgerCollector { std::size_t accepted{0}; @@ -580,11 +590,12 @@ struct LedgerCollector } }; -/** Write out stream of ledger activity - - Writes information about every accepted and fully-validated ledger to a - provided std::ostream. -*/ +/** + * Write out stream of ledger activity + * + * Writes information about every accepted and fully-validated ledger to a + * provided std::ostream. + */ struct StreamCollector { std::ostream& out; @@ -611,11 +622,12 @@ struct StreamCollector } }; -/** Saves information about Jumps for closed and fully validated ledgers. A - jump occurs when a node closes/fully validates a new ledger that is not the - immediate child of the prior closed/fully validated ledgers. This includes - jumps across branches and jumps ahead in the same branch of ledger history. -*/ +/** + * Saves information about Jumps for closed and fully validated ledgers. A + * jump occurs when a node closes/fully validates a new ledger that is not the + * immediate child of the prior closed/fully validated ledgers. This includes + * jumps across branches and jumps ahead in the same branch of ledger history. + */ struct JumpCollector { struct Jump diff --git a/src/test/csf/events.h b/src/test/csf/events.h index dfedd2627a..2cf4fd9e9b 100644 --- a/src/test/csf/events.h +++ b/src/test/csf/events.h @@ -29,58 +29,81 @@ namespace xrpl::test::csf { // CollectorRef.f defines a type-erased holder for arbitrary Collectors. If // any new events are added, the interface there needs to be updated. -/** A value to be flooded to all other peers starting from this peer. +/** + * A value to be flooded to all other peers starting from this peer. */ template struct Share { - //! Event that is shared + /** + * Event that is shared + */ V val; }; -/** A value relayed to another peer as part of flooding +/** + * A value relayed to another peer as part of flooding */ template struct Relay { - //! Peer relaying to + /** + * Peer relaying to + */ PeerID to; - //! The value to relay + /** + * The value to relay + */ V val; }; -/** A value received from another peer as part of flooding +/** + * A value received from another peer as part of flooding */ template struct Receive { - //! Peer that sent the value + /** + * Peer that sent the value + */ PeerID from; - //! The received value + /** + * The received value + */ V val; }; -/** A transaction submitted to a peer */ +/** + * A transaction submitted to a peer + */ struct SubmitTx { - //! The submitted transaction + /** + * The submitted transaction + */ Tx tx; }; -/** Peer starts a new consensus round +/** + * Peer starts a new consensus round */ struct StartRound { - //! The preferred ledger for the start of consensus + /** + * The preferred ledger for the start of consensus + */ Ledger::ID bestLedger{}; - //! The prior ledger on hand + /** + * The prior ledger on hand + */ Ledger prevLedger; }; -/** Peer closed the open ledger +/** + * Peer closed the open ledger */ struct CloseLedger { @@ -91,7 +114,9 @@ struct CloseLedger TxSetType txs; }; -//! Peer accepted consensus results +/** + * Peer accepted consensus results + */ struct AcceptLedger { // The newly created ledger @@ -101,7 +126,9 @@ struct AcceptLedger Ledger prior; }; -//! Peer detected a wrong prior ledger during consensus +/** + * Peer detected a wrong prior ledger during consensus + */ struct WrongPrevLedger { // ID of wrong ledger we had @@ -110,14 +137,20 @@ struct WrongPrevLedger Ledger::ID right; }; -//! Peer fully validated a new ledger +/** + * Peer fully validated a new ledger + */ struct FullyValidateLedger { - //! The new fully validated ledger + /** + * The new fully validated ledger + */ Ledger ledger; - //! The prior fully validated ledger - //! This is a jump if prior.id() != ledger.parentID() + /** + * The prior fully validated ledger + * This is a jump if prior.id() != ledger.parentID() + */ Ledger prior; }; diff --git a/src/test/csf/ledgers.h b/src/test/csf/ledgers.h index 4873519dee..09f5fa54de 100644 --- a/src/test/csf/ledgers.h +++ b/src/test/csf/ledgers.h @@ -22,27 +22,28 @@ namespace xrpl::test::csf { -/** A ledger is a set of observed transactions and a sequence number - identifying the ledger. - - Peers in the consensus process are trying to agree on a set of transactions - to include in a ledger. For simulation, each transaction is a single - integer and the ledger is the set of observed integers. This means future - ledgers have prior ledgers as subsets, e.g. - - Ledger 0 : {} - Ledger 1 : {1,4,5} - Ledger 2 : {1,2,4,5,10} - .... - - Ledgers are immutable value types. All ledgers with the same sequence - number, transactions, close time, etc. will have the same ledger ID. The - LedgerOracle class below manages ID assignments for a simulation and is the - only way to close and create a new ledger. Since the parent ledger ID is - part of type, this also means ledgers with distinct histories will have - distinct ids, even if they have the same set of transactions, sequence - number and close time. -*/ +/** + * A ledger is a set of observed transactions and a sequence number + * identifying the ledger. + * + * Peers in the consensus process are trying to agree on a set of transactions + * to include in a ledger. For simulation, each transaction is a single + * integer and the ledger is the set of observed integers. This means future + * ledgers have prior ledgers as subsets, e.g. + * + * Ledger 0 : {} + * Ledger 1 : {1,4,5} + * Ledger 2 : {1,2,4,5,10} + * .... + * + * Ledgers are immutable value types. All ledgers with the same sequence + * number, transactions, close time, etc. will have the same ledger ID. The + * LedgerOracle class below manages ID assignments for a simulation and is the + * only way to close and create a new ledger. Since the parent ledger ID is + * part of type, this also means ledgers with distinct histories will have + * distinct ids, even if they have the same set of transactions, sequence + * number and close time. + */ class Ledger { friend class LedgerOracle; @@ -74,21 +75,31 @@ private: // Resolution used to determine close time NetClock::duration closeTimeResolution = kLedgerDefaultTimeResolution; - //! When the ledger closed (up to closeTimeResolution) + /** + * When the ledger closed (up to closeTimeResolution) + */ NetClock::time_point closeTime; - //! Whether consensus agreed on the close time + /** + * Whether consensus agreed on the close time + */ bool closeTimeAgree = true; - //! Parent ledger id + /** + * Parent ledger id + */ ID parentID{0}; - //! Parent ledger close time + /** + * Parent ledger close time + */ NetClock::time_point parentCloseTime; - //! IDs of this ledgers ancestors. Since each ledger already has unique - //! ancestors based on the parentID, this member is not needed for any - //! of the operators below. + /** + * IDs of this ledgers ancestors. Since each ledger already has unique + * ancestors based on the parentID, this member is not needed for any + * of the operators below. + */ std::vector ancestors; [[nodiscard]] auto @@ -199,16 +210,20 @@ public: return instance_->txs; } - /** Determine whether ancestor is really an ancestor of this ledger */ + /** + * Determine whether ancestor is really an ancestor of this ledger + */ [[nodiscard]] bool isAncestor(Ledger const& ancestor) const; - /** Return the id of the ancestor with the given seq (if exists/known) + /** + * Return the id of the ancestor with the given seq (if exists/known) */ ID operator[](Seq seq) const; - /** Return the sequence number of the first mismatching ancestor + /** + * Return the sequence number of the first mismatching ancestor */ friend Ledger::Seq mismatch(Ledger const& a, Ledger const& o); @@ -227,7 +242,8 @@ private: Instance const* instance_; }; -/** Oracle maintaining unique ledgers for a simulation. +/** + * Oracle maintaining unique ledgers for a simulation. */ class LedgerOracle { @@ -246,18 +262,21 @@ class LedgerOracle public: LedgerOracle(); - /** Find the ledger with the given ID */ + /** + * Find the ledger with the given ID + */ [[nodiscard]] std::optional lookup(Ledger::ID const& id) const; - /** Accept the given txs and generate a new ledger - - @param curr The current ledger - @param txs The transactions to apply to the current ledger - @param closeTimeResolution Resolution used in determining close time - @param consensusCloseTime The consensus agreed close time, no valid time - if 0 - */ + /** + * Accept the given txs and generate a new ledger + * + * @param curr The current ledger + * @param txs The transactions to apply to the current ledger + * @param closeTimeResolution Resolution used in determining close time + * @param consensusCloseTime The consensus agreed close time, no valid time + * if 0 + */ Ledger accept( Ledger const& curr, @@ -272,38 +291,39 @@ public: return accept(curr, TxSetType{tx}, curr.closeTimeResolution(), curr.closeTime() + 1s); } - /** Determine the number of distinct branches for the set of ledgers. - - Ledgers A and B are on different branches if A != B, A is not an - ancestor of B and B is not an ancestor of A, e.g. - - /--> A - O - \--> B - */ + /** + * Determine the number of distinct branches for the set of ledgers. + * + * Ledgers A and B are on different branches if A != B, A is not an + * ancestor of B and B is not an ancestor of A, e.g. + * + * /--> A + * O + * \--> B + */ static std::size_t branches(std::set const& ledgers); }; -/** Helper for writing unit tests with controlled ledger histories. - - This class allows clients to refer to distinct ledgers as strings, where - each character in the string indicates a unique ledger. It enforces the - uniqueness at runtime, but this simplifies creation of alternate ledger - histories, e.g. - - HistoryHelper hh; - hh["a"] - hh["ab"] - hh["ac"] - hh["abd"] - - Creates a history like - b - d - / - a - c - -*/ +/** + * Helper for writing unit tests with controlled ledger histories. + * + * This class allows clients to refer to distinct ledgers as strings, where + * each character in the string indicates a unique ledger. It enforces the + * uniqueness at runtime, but this simplifies creation of alternate ledger + * histories, e.g. + * + * HistoryHelper hh; + * hh["a"] + * hh["ab"] + * hh["ac"] + * hh["abd"] + * + * Creates a history like + * b - d + * / + * a - c + */ struct LedgerHistoryHelper { LedgerOracle oracle; @@ -316,11 +336,12 @@ struct LedgerHistoryHelper ledgers[""] = Ledger{Ledger::MakeGenesis{}}; } - /** Get or create the ledger with the given string history. - - Creates any necessary intermediate ledgers, but asserts if - a letter is re-used (e.g. "abc" then "adc" would assert) - */ + /** + * Get or create the ledger with the given string history. + * + * Creates any necessary intermediate ledgers, but asserts if + * a letter is re-used (e.g. "abc" then "adc" would assert) + */ Ledger const& operator[](std::string const& s) { diff --git a/src/test/csf/random.h b/src/test/csf/random.h index 9fc51f1217..f8df253642 100644 --- a/src/test/csf/random.h +++ b/src/test/csf/random.h @@ -8,15 +8,16 @@ namespace xrpl::test::csf { -/** Return a randomly shuffled copy of vector based on weights w. - - @param v The set of values - @param w The set of weights of each value - @param g A pseudo-random number generator - @return A vector with entries randomly sampled without replacement - from the original vector based on the provided weights. - I.e. res[0] comes from sample v[i] with weight w[i]/suk_ w[k] -*/ +/** + * Return a randomly shuffled copy of vector based on weights w. + * + * @param v The set of values + * @param w The set of weights of each value + * @param g A pseudo-random number generator + * @return A vector with entries randomly sampled without replacement + * from the original vector based on the provided weights. + * I.e. res[0] comes from sample v[i] with weight w[i]/suk_ w[k] + */ template std::vector randomWeightedShuffle(std::vector v, std::vector w, G& g) @@ -34,14 +35,15 @@ randomWeightedShuffle(std::vector v, std::vector w, G& g) return v; } -/** Generate a vector of random samples - - @param size the size of the sample - @param dist the distribution to sample - @param g the pseudo-random number generator - - @return vector of samples -*/ +/** + * Generate a vector of random samples + * + * @param size the size of the sample + * @param dist the distribution to sample + * @param g the pseudo-random number generator + * + * @return vector of samples + */ template std::vector sample(std::size_t size, RandomNumberDistribution dist, Generator& g) @@ -51,13 +53,14 @@ sample(std::size_t size, RandomNumberDistribution dist, Generator& g) return res; } -/** Invocable that returns random samples from a range according to a discrete - distribution - - Given a pair of random access iterators begin and end, each call to the - instance of Selector returns a random entry in the range (begin,end) - according to the weights provided at construction. -*/ +/** + * Invocable that returns random samples from a range according to a discrete + * distribution + * + * Given a pair of random access iterators begin and end, each call to the + * instance of Selector returns a random entry in the range (begin,end) + * according to the weights provided at construction. + */ template class Selector { @@ -66,12 +69,13 @@ class Selector Generator g_; public: - /** Constructor - @param first Random access iterator to the start of the range - @param last Random access iterator to the end of the range - @param w Vector of weights of size list-first - @param g the pseudo-random number generator - */ + /** + * Constructor + * @param first Random access iterator to the start of the range + * @param last Random access iterator to the end of the range + * @param w Vector of weights of size list-first + * @param g the pseudo-random number generator + */ Selector(RAIter first, RAIter last, std::vector const& w, Generator& g) : first_{first}, last_{last}, dd_{w.begin(), w.end()}, g_{g} { @@ -100,7 +104,8 @@ makeSelector(Iter first, Iter last, std::vector const& w, Generator& g) //------------------------------------------------------------------------------ // Additional distributions of interest not defined in -/** Constant "distribution" that always returns the same value +/** + * Constant "distribution" that always returns the same value */ class ConstantDistribution { @@ -119,11 +124,12 @@ public: } }; -/** Power-law distribution with PDF - - P(x) = (x/xmin)^-a - - for a >= 1 and xmin >= 1 +/** + * Power-law distribution with PDF + * + * P(x) = (x/xmin)^-a + * + * for a >= 1 and xmin >= 1 */ class PowerLawDistribution { @@ -135,9 +141,8 @@ class PowerLawDistribution public: using result_type = double; - PowerLawDistribution(double xmin, double a) : xmin_{xmin}, a_{a} + PowerLawDistribution(double xmin, double a) : xmin_{xmin}, a_{a}, inv_(1.0 / (1.0 - a_)) { - inv_ = 1.0 / (1.0 - a_); } template diff --git a/src/test/csf/submitters.h b/src/test/csf/submitters.h index a71c849fb7..160d0bcd9f 100644 --- a/src/test/csf/submitters.h +++ b/src/test/csf/submitters.h @@ -13,7 +13,9 @@ namespace xrpl::test::csf { // Submitters are classes for simulating submission of transactions to the // network -/** Represents rate as a count/duration */ +/** + * Represents rate as a count/duration + */ struct Rate { std::size_t count; @@ -26,23 +28,24 @@ struct Rate } }; -/** Submits transactions to a specified peer - - Submits successive transactions beginning at start, then spaced according - to successive calls of distribution(), until stop. - - @tparam Distribution is a `UniformRandomBitGenerator` from the STL that - is used by random distributions to generate random samples - @tparam Generator is an object with member - - T operator()(Generator &g) - - which generates the delay T in SimDuration units to the next - transaction. For the current definition of SimDuration, this is - currently the number of nanoseconds. Submitter internally casts - arithmetic T to SimDuration::rep units to allow using standard - library distributions as a Distribution. -*/ +/** + * Submits transactions to a specified peer + * + * Submits successive transactions beginning at start, then spaced according + * to successive calls of distribution(), until stop. + * + * @tparam Distribution is a `UniformRandomBitGenerator` from the STL that + * is used by random distributions to generate random samples + * @tparam Generator is an object with member + * + * T operator()(Generator &g) + * + * which generates the delay T in SimDuration units to the next + * transaction. For the current definition of SimDuration, this is + * currently the number of nanoseconds. Submitter internally casts + * arithmetic T to SimDuration::rep units to allow using standard + * library distributions as a Distribution. + */ template class Submitter { diff --git a/src/test/csf/timers.h b/src/test/csf/timers.h index 2f86fe7729..4f13b21b25 100644 --- a/src/test/csf/timers.h +++ b/src/test/csf/timers.h @@ -12,7 +12,8 @@ namespace xrpl::test::csf { // Timers are classes that schedule repeated events and are mostly independent // of simulation-specific details. -/** Gives heartbeat of simulation to signal simulation progression +/** + * Gives heartbeat of simulation to signal simulation progression */ class HeartbeatTimer { diff --git a/src/test/jtx/AMM.h b/src/test/jtx/AMM.h index 605f15f812..68b6d9f745 100644 --- a/src/test/jtx/AMM.h +++ b/src/test/jtx/AMM.h @@ -131,7 +131,8 @@ struct ClawbackArg std::optional err = std::nullopt; }; -/** Convenience class to test AMM functionality. +/** + * Convenience class to test AMM functionality. */ class AMM { @@ -187,7 +188,8 @@ public: STAmount const& asset2, std::uint16_t const& tfee); - /** Send amm_info RPC command + /** + * Send amm_info RPC command */ [[nodiscard]] json::Value ammRpcInfo( @@ -209,7 +211,8 @@ public: bool ignoreParams, unsigned apiVersion) const; - /** Verify the AMM balances. + /** + * Verify the AMM balances. */ [[nodiscard]] bool expectBalances( @@ -218,7 +221,8 @@ public: IOUAmount const& lpt, std::optional const& account = std::nullopt) const; - /** Get AMM balances for the token pair. + /** + * Get AMM balances for the token pair. */ [[nodiscard]] std::tuple balances( diff --git a/src/test/jtx/AMMTest.h b/src/test/jtx/AMMTest.h index 971cc5db84..9385313225 100644 --- a/src/test/jtx/AMMTest.h +++ b/src/test/jtx/AMMTest.h @@ -103,7 +103,8 @@ public: } protected: - /** testAMM() funds 30,000XRP and 30,000IOU + /** + * testAMM() funds 30,000XRP and 30,000IOU * for each non-XRP asset to Alice and Carol */ void diff --git a/src/test/jtx/AbstractClient.h b/src/test/jtx/AbstractClient.h index 7c7107ca79..f9d8de0768 100644 --- a/src/test/jtx/AbstractClient.h +++ b/src/test/jtx/AbstractClient.h @@ -20,21 +20,24 @@ public: AbstractClient& operator=(AbstractClient const&) = delete; - /** Submit a command synchronously. - - The arguments to the function and the returned JSON - are in a normalized format, the same whether the client - is using the JSON-RPC over HTTP/S or WebSocket transport. - - @param cmd The command to execute - @param params json::Value of null or object type - with zero or more key/value pairs. - @return The server response in normalized format. - */ + /** + * Submit a command synchronously. + * + * The arguments to the function and the returned JSON + * are in a normalized format, the same whether the client + * is using the JSON-RPC over HTTP/S or WebSocket transport. + * + * @param cmd The command to execute + * @param params json::Value of null or object type + * with zero or more key/value pairs. + * @return The server response in normalized format. + */ virtual json::Value invoke(std::string const& cmd, json::Value const& params = {}) = 0; - /// Get RPC 1.0 or RPC 2.0 + /** + * Get RPC 1.0 or RPC 2.0 + */ [[nodiscard]] virtual unsigned version() const = 0; }; diff --git a/src/test/jtx/Account.h b/src/test/jtx/Account.h index 264a39f08b..60d07beebd 100644 --- a/src/test/jtx/Account.h +++ b/src/test/jtx/Account.h @@ -14,7 +14,9 @@ namespace xrpl::test::jtx { class IOU; -/** Immutable cryptographic account descriptor. */ +/** + * Immutable cryptographic account descriptor. + */ class Account { private: @@ -24,7 +26,9 @@ private: }; public: - /** The master account. */ + /** + * The master account. + */ static Account const kMaster; Account() = delete; @@ -35,7 +39,9 @@ public: Account& operator=(Account&&) = default; - /** Create an account from a simple string name. */ + /** + * Create an account from a simple string name. + */ /** @{ */ Account(std::string name, KeyType type = KeyType::Secp256k1); @@ -50,63 +56,79 @@ public: /** @} */ - /** Create an Account from an account ID. Should only be used when the - * secret key is unavailable, such as for pseudo-accounts. */ + /** + * Create an Account from an account ID. Should only be used when the + * secret key is unavailable, such as for pseudo-accounts. + */ explicit Account(std::string name, AccountID const& id); enum class AcctStringType { Base58Seed, Other }; - /** Create an account from a base58 seed string. Throws on invalid seed. */ + /** + * Create an account from a base58 seed string. Throws on invalid seed. + */ Account(AcctStringType stringType, std::string base58SeedStr); - /** Return the name */ + /** + * Return the name + */ [[nodiscard]] std::string const& name() const { return name_; } - /** Return the public key. */ + /** + * Return the public key. + */ [[nodiscard]] PublicKey const& pk() const { return pk_; } - /** Return the secret key. */ + /** + * Return the secret key. + */ [[nodiscard]] SecretKey const& sk() const { return sk_; } - /** Returns the Account ID. - - The Account ID is the uint160 hash of the public key. - */ + /** + * Returns the Account ID. + * + * The Account ID is the uint160 hash of the public key. + */ [[nodiscard]] AccountID id() const { return id_; } - /** Returns the human readable public key. */ + /** + * Returns the human readable public key. + */ [[nodiscard]] std::string const& human() const { return human_; } - /** Implicit conversion to AccountID. - - This allows passing an Account - where an AccountID is expected. - */ + /** + * Implicit conversion to AccountID. + * + * This allows passing an Account + * where an AccountID is expected. + */ operator AccountID() const { return id_; } - /** Returns an IOU for the specified gateway currency. */ + /** + * Returns an IOU for the specified gateway currency. + */ IOU operator[](std::string const& s) const; diff --git a/src/test/jtx/CheckMessageLogs.h b/src/test/jtx/CheckMessageLogs.h index fc46f41671..24b2c24a73 100644 --- a/src/test/jtx/CheckMessageLogs.h +++ b/src/test/jtx/CheckMessageLogs.h @@ -9,7 +9,8 @@ namespace xrpl::test { -/** Log manager that searches for a specific message substring +/** + * Log manager that searches for a specific message substring */ class CheckMessageLogs : public Logs { @@ -41,12 +42,13 @@ class CheckMessageLogs : public Logs }; public: - /** Constructor - - @param msg The message string to search for - @param pFound Pointer to the variable to set to true if the message is - found - */ + /** + * Constructor + * + * @param msg The message string to search for + * @param pFound Pointer to the variable to set to true if the message is + * found + */ CheckMessageLogs(std::string msg, bool* pFound) : Logs{beast::Severity::Debug}, msg_{std::move(msg)}, pFound_{pFound} { diff --git a/src/test/jtx/Env.h b/src/test/jtx/Env.h index ba28d19738..7e22cdd571 100644 --- a/src/test/jtx/Env.h +++ b/src/test/jtx/Env.h @@ -55,14 +55,15 @@ namespace xrpl::test::jtx { -/** Wrapper that captures std::source_location when implicitly constructed. - This solves the problem of combining std::source_location with variadic - templates. The std::source_location default argument is evaluated at the - call site when the wrapper is constructed via implicit conversion. - - This is a template struct that holds the value directly, allowing implicit - conversion without template argument deduction issues via CTAD. -*/ +/** + * Wrapper that captures std::source_location when implicitly constructed. + * This solves the problem of combining std::source_location with variadic + * templates. The std::source_location default argument is evaluated at the + * call site when the wrapper is constructed via implicit conversion. + * + * This is a template struct that holds the value directly, allowing implicit + * conversion without template argument deduction issues via CTAD. + */ template struct WithSourceLocation { @@ -77,7 +78,9 @@ struct WithSourceLocation } }; -/** Designate accounts as no-ripple in Env::fund */ +/** + * Designate accounts as no-ripple in Env::fund + */ template std::array noripple(Account const& account, Args const&... args) @@ -151,7 +154,9 @@ public: //------------------------------------------------------------------------------ -/** A transaction testing environment. */ +/** + * A transaction testing environment. + */ class Env { public: @@ -159,7 +164,9 @@ public: Account const& master = Account::kMaster; - /// Used by parseResult() and postConditions() + /** + * Used by parseResult() and postConditions() + */ struct ParsedResult { std::optional ter; @@ -308,11 +315,12 @@ public: return *bundle_.timeKeeper; } - /** Returns the current network time - - @note This is manually advanced when ledgers - close or by callers. - */ + /** + * Returns the current network time + * + * @note This is manually advanced when ledgers + * close or by callers. + */ NetClock::time_point // NOLINTNEXTLINE(readability-make-member-function-const) now() @@ -320,7 +328,9 @@ public: return timeKeeper().now(); } - /** Returns the connected client. */ + /** + * Returns the connected client. + */ AbstractClient& // NOLINTNEXTLINE(readability-make-member-function-const) client() @@ -328,11 +338,12 @@ public: return *bundle_.client; } - /** Execute an RPC command. - - The command is examined and used to build - the correct JSON as per the arguments. - */ + /** + * Execute an RPC command. + * + * The command is examined and used to build + * the correct JSON as per the arguments. + */ template json::Value rpc(unsigned apiVersion, @@ -354,61 +365,64 @@ public: json::Value rpc(std::string const& cmd, Args&&... args); - /** Returns the current ledger. - - This is a non-modifiable snapshot of the - open ledger at the moment of the call. - Transactions applied after the call to open() - will not be visible. - - */ + /** + * Returns the current ledger. + * + * This is a non-modifiable snapshot of the + * open ledger at the moment of the call. + * Transactions applied after the call to open() + * will not be visible. + */ [[nodiscard]] std::shared_ptr current() const { return app().getOpenLedger().current(); } - /** Returns the last closed ledger. - - The open ledger is built on top of the - last closed ledger. When the open ledger - is closed, it becomes the new closed ledger - and a new open ledger takes its place. - */ + /** + * Returns the last closed ledger. + * + * The open ledger is built on top of the + * last closed ledger. When the open ledger + * is closed, it becomes the new closed ledger + * and a new open ledger takes its place. + */ std::shared_ptr closed(); - /** Close and advance the ledger. - - The resulting close time will be different and - greater than the previous close time, and at or - after the passed-in close time. - - Effects: - - Creates a new closed ledger from the last - closed ledger. - - All transactions that made it into the open - ledger are applied to the closed ledger. - - The Application network time is set to - the close time of the resulting ledger. - - @return true if no error, false if error - */ + /** + * Close and advance the ledger. + * + * The resulting close time will be different and + * greater than the previous close time, and at or + * after the passed-in close time. + * + * Effects: + * + * Creates a new closed ledger from the last + * closed ledger. + * + * All transactions that made it into the open + * ledger are applied to the closed ledger. + * + * The Application network time is set to + * the close time of the resulting ledger. + * + * @return true if no error, false if error + */ bool close( NetClock::time_point closeTime, std::optional consensusDelay = std::nullopt); - /** Close and advance the ledger. - - The time is calculated as the duration from - the previous ledger closing time. - - @return true if no error, false if error - */ + /** + * Close and advance the ledger. + * + * The time is calculated as the duration from + * the previous ledger closing time. + * + * @return true if no error, false if error + */ template bool close(std::chrono::duration const& elapsed) @@ -417,13 +431,14 @@ public: return close(now() + elapsed); } - /** Close and advance the ledger. - - The time is calculated as five seconds from - the previous ledger closing time. - - @return true if no error, false if error - */ + /** + * Close and advance the ledger. + * + * The time is calculated as five seconds from + * the previous ledger closing time. + * + * @return true if no error, false if error + */ bool close() { @@ -431,34 +446,35 @@ public: return close(std::chrono::seconds(5)); } - /** Close and advance the ledger, then synchronize with the server's - io_context to ensure all async operations initiated by the close have - been started. - - This function performs the same ledger close as close(), but additionally - ensures that all tasks posted to the server's io_context (such as - WebSocket subscription message sends) have been initiated before returning. - - What it guarantees: - - All async operations posted before syncClose() have been STARTED - - For WebSocket sends: async_write_some() has been called - - The actual I/O completion may still be pending (async) - - What it does NOT guarantee: - - Async operations have COMPLETED - - WebSocket messages have been received by clients - - However, for localhost connections, the remaining latency is typically - microseconds, making tests reliable - - Use this instead of close() when: - - Test code immediately checks for subscription messages - - Race conditions between test and worker threads must be avoided - - Deterministic test behavior is required - - @param timeout Maximum time to wait for the barrier task to execute - @return true if close succeeded and barrier executed within timeout, - false otherwise - */ + /** + * Close and advance the ledger, then synchronize with the server's + * io_context to ensure all async operations initiated by the close have + * been started. + * + * This function performs the same ledger close as close(), but additionally + * ensures that all tasks posted to the server's io_context (such as + * WebSocket subscription message sends) have been initiated before returning. + * + * What it guarantees: + * - All async operations posted before syncClose() have been STARTED + * - For WebSocket sends: async_write_some() has been called + * - The actual I/O completion may still be pending (async) + * + * What it does NOT guarantee: + * - Async operations have COMPLETED + * - WebSocket messages have been received by clients + * - However, for localhost connections, the remaining latency is typically + * microseconds, making tests reliable + * + * Use this instead of close() when: + * - Test code immediately checks for subscription messages + * - Race conditions between test and worker threads must be avoided + * - Deterministic test behavior is required + * + * @param timeout Maximum time to wait for the barrier task to execute + * @return true if close succeeded and barrier executed within timeout, + * false otherwise + */ [[nodiscard]] bool syncClose(std::chrono::steady_clock::duration timeout = std::chrono::seconds{1}) { @@ -473,16 +489,19 @@ public: return result && status == std::future_status::ready; } - /** Turn on JSON tracing. - With no arguments, trace all - */ + /** + * Turn on JSON tracing. + * With no arguments, trace all + */ void trace(int howMany = -1) { trace_ = howMany; } - /** Turn off JSON tracing. */ + /** + * Turn off JSON tracing. + */ void notrace() { @@ -495,7 +514,9 @@ public: parseFailureExpected_ = b; } - /** Turn off signature checks. */ + /** + * Turn off signature checks. + */ void disableSigs() { @@ -516,11 +537,15 @@ public: return retries_; } - /** Associate AccountID with account. */ + /** + * Associate AccountID with account. + */ void memoize(Account const& account); - /** Returns the Account given the AccountID. */ + /** + * Returns the Account given the AccountID. + */ /** @{ */ [[nodiscard]] Account const& lookup(AccountID const& id) const; @@ -529,51 +554,84 @@ public: lookup(std::string const& base58ID) const; /** @} */ - /** Returns the XRP balance on an account. - Returns 0 if the account does not exist. - */ + /** + * Returns the XRP balance on an account. + * Returns 0 if the account does not exist. + */ [[nodiscard]] PrettyAmount balance(Account const& account) const; - /** Returns the next sequence number on account. - Exceptions: - Throws if the account does not exist - */ + /** + * Returns the next sequence number on account. + * + * @throws if the account does not exist + */ [[nodiscard]] std::uint32_t seq(Account const& account) const; - /** Return the balance on an account. - Returns 0 if the trust line does not exist. - */ + /** + * Return the balance on an account. + * Returns 0 if the trust line does not exist. + */ // VFALCO NOTE This should return a unit-less amount [[nodiscard]] PrettyAmount balance(Account const& account, Asset const& asset) const; - /** Returns the IOU limit on an account. - Returns 0 if the trust line does not exist. - */ + /** + * Returns the IOU limit on an account. + * Returns 0 if the trust line does not exist. + */ [[nodiscard]] PrettyAmount limit(Account const& account, Issue const& issue) const; - /** Return the number of objects owned by an account. + /** + * Return the number of objects owned by an account. * Returns 0 if the account does not exist. */ [[nodiscard]] std::uint32_t ownerCount(Account const& account) const; - /** Return an account root. - @return empty if the account does not exist. - */ + /** + * Return the number of sponsored objects owned by an account. + * + * @throws if the account does not exist. + */ + [[nodiscard]] std::uint32_t + sponsoredOwnerCount(Account const& account) const; + + /** + * Return the number of sponsoring objects owned by an account. + * + * @throws if the account does not exist. + */ + [[nodiscard]] std::uint32_t + sponsoringOwnerCount(Account const& account) const; + + /** + * Return the number of sponsoring accounts owned by an account. + * + * @throws if the account does not exist. + */ + [[nodiscard]] std::uint32_t + sponsoringAccountCount(Account const& account) const; + + /** + * Return an account root. + * @return empty if the account does not exist. + */ [[nodiscard]] SLE::const_pointer le(Account const& account) const; - /** Return a ledger entry. - @return empty if the ledger entry does not exist - */ + /** + * Return a ledger entry. + * @return empty if the ledger entry does not exist + */ [[nodiscard]] SLE::const_pointer le(Keylet const& k) const; - /** Create a JTx from parameters. */ + /** + * Create a JTx from parameters. + */ template JTx jt(JsonValue&& jv, FN const&... fN) @@ -585,7 +643,9 @@ public: return jt; } - /** Create a JTx from parameters. */ + /** + * Create a JTx from parameters. + */ template JTx jtnofill(JsonValue&& jv, FN const&... fN) @@ -597,9 +657,10 @@ public: return jt; } - /** Create JSON from parameters. - This will apply funclets and autofill. - */ + /** + * Create JSON from parameters. + * This will apply funclets and autofill. + */ template json::Value json(JsonValue&& jv, FN const&... fN) @@ -608,11 +669,12 @@ public: return std::move(tj.jv); } - /** Check a set of requirements. - - The requirements are formed - from condition functors. - */ + /** + * Check a set of requirements. + * + * The requirements are formed + * from condition functors. + */ template void require(Args const&... args) @@ -620,29 +682,33 @@ public: jtx::required(args...)(*this); } - /** Gets the TER result and `didApply` flag from a RPC Json result object. + /** + * Gets the TER result and `didApply` flag from a RPC Json result object. */ static ParsedResult parseResult(json::Value const& jr); - /** Submit an existing JTx. - This calls postconditions. - */ + /** + * Submit an existing JTx. + * This calls postconditions. + */ virtual void submit(JTx const& jt, std::source_location const& loc = std::source_location::current()); - /** Use the submit RPC command with a provided JTx object. - This calls postconditions. - */ + /** + * Use the submit RPC command with a provided JTx object. + * This calls postconditions. + */ void signAndSubmit( JTx const& jt, json::Value params = json::ValueType::Null, std::source_location const& loc = std::source_location::current()); - /** Check expected postconditions - of JTx submission. - */ + /** + * Check expected postconditions + * of JTx submission. + */ void postconditions( JTx const& jt, @@ -650,7 +716,9 @@ public: json::Value const& jr = json::Value(), std::source_location const& loc = std::source_location::current()); - /** Apply funclets and submit. */ + /** + * Apply funclets and submit. + */ /** @{ */ template Env& @@ -683,38 +751,42 @@ public: } /** @} */ - /** Return the TER for the last JTx. */ + /** + * Return the TER for the last JTx. + */ [[nodiscard]] TER ter() const { return ter_; } - /** Return metadata for the last JTx. + /** + * Return metadata for the last JTx. * - * NOTE: this has a side effect of closing the open ledger. - * The ledger will only be closed if it includes transactions. + * NOTE: this has a side effect of closing the open ledger. + * The ledger will only be closed if it includes transactions. * - * Effects: + * Effects: * - * The open ledger is closed as if by a call - * to close(). The metadata for the last - * transaction ID, if any, is returned. + * The open ledger is closed as if by a call + * to close(). The metadata for the last + * transaction ID, if any, is returned. */ std::shared_ptr meta(); - /** Return the tx data for the last JTx. - - Effects: - - The tx data for the last transaction - ID, if any, is returned. No side - effects. - - @note Only necessary for JTx submitted - with via sign-and-submit method. - */ + /** + * Return the tx data for the last JTx. + * + * Effects: + * + * The tx data for the last transaction + * ID, if any, is returned. No side + * effects. + * + * @note Only necessary for JTx submitted + * with via sign-and-submit method. + */ [[nodiscard]] std::shared_ptr tx() const; @@ -749,32 +821,33 @@ private: } public: - /** Create a new account with some XRP. - - These convenience functions are for easy set-up - of the environment, they bypass fee, seq, and sig - settings. The XRP is transferred from the master - account. - - Preconditions: - The account must not already exist - - Effects: - The asfDefaultRipple on the account is set, - and the sequence number is incremented, unless - the account is wrapped with a call to noripple. - - The account's XRP balance is set to amount. - - Generates a test that the balance is set. - - @param amount The amount of XRP to transfer to - each account. - - @param args A heterogeneous list of accounts to fund - or calls to noripple with lists of accounts - to fund. - */ + /** + * Create a new account with some XRP. + * + * These convenience functions are for easy set-up + * of the environment, they bypass fee, seq, and sig + * settings. The XRP is transferred from the master + * account. + * + * Preconditions: + * The account must not already exist + * + * Effects: + * The asfDefaultRipple on the account is set, + * and the sequence number is incremented, unless + * the account is wrapped with a call to noripple. + * + * The account's XRP balance is set to amount. + * + * Generates a test that the balance is set. + * + * @param amount The amount of XRP to transfer to + * each account. + * + * @param args A heterogeneous list of accounts to fund + * or calls to noripple with lists of accounts + * to fund. + */ template void fund(STAmount const& amount, Arg const& arg, Args const&... args) @@ -784,23 +857,24 @@ public: fund(amount, args...); } - /** Establish trust lines. - - These convenience functions are for easy set-up - of the environment, they bypass fee, seq, and sig - settings. - - Preconditions: - The account must already exist - - Effects: - A trust line is added for the account. - The account's sequence number is incremented. - The account is refunded for the transaction fee - to set the trust line. - - The refund comes from the master account. - */ + /** + * Establish trust lines. + * + * These convenience functions are for easy set-up + * of the environment, they bypass fee, seq, and sig + * settings. + * + * Preconditions: + * The account must already exist + * + * Effects: + * A trust line is added for the account. + * The account's sequence number is incremented. + * The account is refunded for the transaction fee + * to set the trust line. + * + * The refund comes from the master account. + */ /** @{ */ void trust(STAmount const& amount, Account const& account); @@ -814,10 +888,11 @@ public: } /** @} */ - /** Create a STTx from a JTx without sanitizing - Use to inject bogus values into test transactions by first - editing the JSON. - */ + /** + * Create a STTx from a JTx without sanitizing + * Use to inject bogus values into test transactions by first + * editing the JSON. + */ std::shared_ptr ust(JTx const& jt); @@ -841,13 +916,14 @@ protected: virtual void autofill(JTx& jt); - /** Create a STTx from a JTx - The framework requires that JSON is valid. - On a parse error, the JSON is logged and - an exception thrown. - Throws: - ParseError - */ + /** + * Create a STTx from a JTx + * The framework requires that JSON is valid. + * On a parse error, the JSON is logged and + * an exception thrown. + * + * @throws ParseError + */ std::shared_ptr st(JTx const& jt); diff --git a/src/test/jtx/Env_ss.h b/src/test/jtx/Env_ss.h index 16e1cdbc82..ca298f0069 100644 --- a/src/test/jtx/Env_ss.h +++ b/src/test/jtx/Env_ss.h @@ -10,10 +10,11 @@ namespace xrpl::test::jtx { -/** A transaction testing environment wrapper. - Transactions submitted in sign-and-submit mode - by default. -*/ +/** + * A transaction testing environment wrapper. + * Transactions submitted in sign-and-submit mode + * by default. + */ class EnvSs { private: diff --git a/src/test/jtx/JSONRPCClient.h b/src/test/jtx/JSONRPCClient.h index 2864cbbdc8..10e567e91f 100644 --- a/src/test/jtx/JSONRPCClient.h +++ b/src/test/jtx/JSONRPCClient.h @@ -8,7 +8,9 @@ namespace xrpl::test { -/** Returns a client using JSON-RPC over HTTP/S. */ +/** + * Returns a client using JSON-RPC over HTTP/S. + */ std::unique_ptr makeJSONRPCClient(Config const& cfg, unsigned rpcVersion = 2); diff --git a/src/test/jtx/JTx.h b/src/test/jtx/JTx.h index da105e01aa..794334bec7 100644 --- a/src/test/jtx/JTx.h +++ b/src/test/jtx/JTx.h @@ -19,9 +19,10 @@ namespace xrpl::test::jtx { class Env; -/** Execution context for applying a JSON transaction. - This augments the transaction with various settings. -*/ +/** + * Execution context for applying a JSON transaction. + * This augments the transaction with various settings. + */ struct JTx { json::Value jv; @@ -37,7 +38,7 @@ struct JTx // Functions that sign the transaction from the Account std::vector> mainSigners; // Functions that sign something else after the mainSigners, such as - // sfCounterpartySignature + // sfCounterpartySignature and sfSponsorSignature std::vector> postSigners; JTx() = default; @@ -63,10 +64,11 @@ struct JTx return jv[key]; } - /** Return a property if it exists - - @return nullptr if the Prop does not exist - */ + /** + * Return a property if it exists + * + * @return nullptr if the Prop does not exist + */ /** @{ */ template Prop* @@ -93,10 +95,11 @@ struct JTx } /** @} */ - /** Set a property - If the property already exists, - it is replaced. - */ + /** + * Set a property + * If the property already exists, + * it is replaced. + */ /** @{ */ void set(std::unique_ptr p) diff --git a/src/test/jtx/Oracle.h b/src/test/jtx/Oracle.h index 2c296ba705..d0fe8104e5 100644 --- a/src/test/jtx/Oracle.h +++ b/src/test/jtx/Oracle.h @@ -102,7 +102,8 @@ struct RemoveArg // validation {close-maxLastUpdateTimeDelta,close+maxLastUpdateTimeDelta}. static constexpr std::chrono::seconds kTestStartTime = kEpochOffset + std::chrono::seconds(10'000); -/** Oracle class facilitates unit-testing of the Price Oracle feature. +/** + * Oracle class facilitates unit-testing of the Price Oracle feature. * It defines functions to create, update, and delete the Oracle object, * to query for various states, and to call APIs. */ diff --git a/src/test/jtx/PathSet.h b/src/test/jtx/PathSet.h index fa3f0d40f9..42d391c46f 100644 --- a/src/test/jtx/PathSet.h +++ b/src/test/jtx/PathSet.h @@ -18,7 +18,8 @@ namespace xrpl::test { -/** Count offer +/** + * Count offer */ inline std::size_t countOffers( @@ -52,7 +53,8 @@ countOffers( return count; } -/** An offer exists +/** + * An offer exists */ inline bool isOffer( @@ -64,7 +66,8 @@ isOffer( return countOffers(env, account, takerPays, takerGets) > 0; } -/** An offer exists +/** + * An offer exists */ inline bool isOffer(jtx::Env& env, jtx::Account const& account, Asset const& takerPays, Asset const& takerGets) diff --git a/src/test/jtx/TestHelpers.h b/src/test/jtx/TestHelpers.h index cb2193db99..c4991c4ff3 100644 --- a/src/test/jtx/TestHelpers.h +++ b/src/test/jtx/TestHelpers.h @@ -51,11 +51,12 @@ namespace xrpl::test::jtx { -/** Generic helper class for helper classes that set a field on a JTx. - - Not every helper will be able to use this because of conversions and other - issues, but for classes where it's straightforward, this can simplify things. -*/ +/** + * Generic helper class for helper classes that set a field on a JTx. + * + * Not every helper will be able to use this because of conversions and other + * issues, but for classes where it's straightforward, this can simplify things. + */ template < class SField, // NOLINTNEXTLINE(readability-redundant-typename): typename required by MSVC @@ -306,7 +307,8 @@ using valueUnitWrapper = JTxFieldWrapper using simpleField = JTxFieldWrapper>; -/** General field definitions, or fields used in multiple transaction namespaces +/** + * General field definitions, or fields used in multiple transaction namespaces */ auto const kData = JTxFieldWrapper(sfData); @@ -379,6 +381,18 @@ checkArraySize(json::Value const& val, unsigned int size); std::uint32_t ownerCount(test::jtx::Env const& env, test::jtx::Account const& account); +// Helper function that returns the sponsored owner count on an account. +std::uint32_t +sponsoredOwnerCount(test::jtx::Env const& env, test::jtx::Account const& account); + +// Helper function that returns the sponsoring owner count on an account. +std::uint32_t +sponsoringOwnerCount(test::jtx::Env const& env, test::jtx::Account const& account); + +// Helper function that returns the sponsoring account count on an account. +std::uint32_t +sponsoringAccountCount(test::jtx::Env const& env, test::jtx::Account const& account); + [[nodiscard]] inline bool checkVL(Slice const& result, std::string const& expected) @@ -741,7 +755,9 @@ equal(Strand const& strand, Args&&... args) /***************************************************************/ namespace check { -/** Create a check. */ +/** + * Create a check. + */ template requires std::is_same_v json::Value @@ -961,7 +977,9 @@ pay(AccountID const& account, } // namespace loan -/** Set Expiration on a JTx. */ +/** + * Set Expiration on a JTx. + */ class Expiration { private: @@ -980,7 +998,9 @@ public: } }; -/** Set SourceTag on a JTx. */ +/** + * Set SourceTag on a JTx. + */ class SourceTag { private: @@ -998,7 +1018,9 @@ public: } }; -/** Set DestinationTag on a JTx. */ +/** + * Set DestinationTag on a JTx. + */ class DestTag { private: diff --git a/src/test/jtx/WSClient.h b/src/test/jtx/WSClient.h index 4aa41e072c..ee5a46e9ae 100644 --- a/src/test/jtx/WSClient.h +++ b/src/test/jtx/WSClient.h @@ -18,18 +18,24 @@ namespace xrpl::test { class WSClient : public AbstractClient { public: - /** Retrieve a message. */ + /** + * Retrieve a message. + */ virtual std::optional getMsg(std::chrono::milliseconds const& timeout = std::chrono::milliseconds{0}) = 0; - /** Retrieve a message that meets the predicate criteria. */ + /** + * Retrieve a message that meets the predicate criteria. + */ virtual std::optional findMsg( std::chrono::milliseconds const& timeout, std::function pred) = 0; }; -/** Returns a client operating through WebSockets/S. */ +/** + * Returns a client operating through WebSockets/S. + */ std::unique_ptr makeWSClient( Config const& cfg, diff --git a/src/test/jtx/acctdelete.h b/src/test/jtx/acctdelete.h index 3f8b9f1ed2..935a97ac65 100644 --- a/src/test/jtx/acctdelete.h +++ b/src/test/jtx/acctdelete.h @@ -9,7 +9,9 @@ namespace xrpl::test::jtx { -/** Delete account. If successful transfer remaining XRP to dest. */ +/** + * Delete account. If successful transfer remaining XRP to dest. + */ json::Value acctdelete(Account const& account, Account const& dest); diff --git a/src/test/jtx/amount.h b/src/test/jtx/amount.h index db7dbd1cc1..57a4502db9 100644 --- a/src/test/jtx/amount.h +++ b/src/test/jtx/amount.h @@ -69,10 +69,11 @@ struct None // could change that value (however unlikely). constexpr XRPAmount kJtxDropsPerXrp{1'000'000}; -/** Represents an XRP, IOU, or MPT quantity - This customizes the string conversion and supports - XRP conversions from integer and floating point. -*/ +/** + * Represents an XRP, IOU, or MPT quantity + * This customizes the string conversion and supports + * XRP conversions from integer and floating point. + */ struct PrettyAmount { private: @@ -91,7 +92,9 @@ public: { } - /** drops */ + /** + * drops + */ template PrettyAmount(T v) requires(sizeof(T) >= sizeof(int) && std::is_integral_v && std::is_signed_v) @@ -99,7 +102,9 @@ public: { } - /** drops */ + /** + * drops + */ template PrettyAmount(T v) requires(sizeof(T) >= sizeof(int) && std::is_unsigned_v) @@ -107,7 +112,9 @@ public: { } - /** drops */ + /** + * drops + */ PrettyAmount(XRPAmount v) : amount_(v) { } @@ -253,11 +260,12 @@ struct BookSpec struct XrpT { - /** Implicit conversion to Issue. - - This allows passing XRP where - an Issue is expected. - */ + /** + * Implicit conversion to Issue. + * + * This allows passing XRP where + * an Issue is expected. + */ operator Issue() const { return xrpIssue(); @@ -273,11 +281,12 @@ struct XrpT return true; } - /** Returns an amount of XRP as PrettyAmount, - which is trivially convertible to STAmount - - @param v The number of XRP (not drops) - */ + /** + * Returns an amount of XRP as PrettyAmount, + * which is trivially convertible to STAmount + * + * @param v The number of XRP (not drops) + */ /** @{ */ template PrettyAmount @@ -288,11 +297,12 @@ struct XrpT return {TOut{v} * kJtxDropsPerXrp}; } - /** Returns an amount of XRP as PrettyAmount, - which is trivially convertible to STAmount - - @param v The Number of XRP (not drops). May be fractional. - */ + /** + * Returns an amount of XRP as PrettyAmount, + * which is trivially convertible to STAmount + * + * @param v The Number of XRP (not drops). May be fractional. + */ PrettyAmount operator()(Number v) const { @@ -321,7 +331,9 @@ struct XrpT } /** @} */ - /** Returns None-of-XRP */ + /** + * Returns None-of-XRP + */ None operator()(NoneT) const { @@ -335,19 +347,21 @@ struct XrpT } }; -/** Converts to XRP Issue or STAmount. - - Examples: - XRP Converts to the XRP Issue - XRP(10) Returns STAmount of 10 XRP -*/ +/** + * Converts to XRP Issue or STAmount. + * + * Examples: + * XRP Converts to the XRP Issue + * XRP(10) Returns STAmount of 10 XRP + */ extern XrpT const XRP; // NOLINT(readability-identifier-naming) -/** Returns an XRP PrettyAmount, which is trivially convertible to STAmount. - - Example: - drops(10) Returns PrettyAmount of 10 drops -*/ +/** + * Returns an XRP PrettyAmount, which is trivially convertible to STAmount. + * + * Example: + * drops(10) Returns PrettyAmount of 10 drops + */ template PrettyAmount drops(Integer i) @@ -356,11 +370,12 @@ drops(Integer i) return {i}; } -/** Returns an XRP PrettyAmount, which is trivially convertible to STAmount. - -Example: -drops(view->fee().basefee) Returns PrettyAmount of 10 drops -*/ +/** + * Returns an XRP PrettyAmount, which is trivially convertible to STAmount. + * + * Example: + * drops(view->fee().basefee) Returns PrettyAmount of 10 drops + */ inline PrettyAmount drops(XRPAmount i) { @@ -383,13 +398,14 @@ struct EpsilonT static EpsilonT const kEpsilon; -/** Converts to IOU Issue or STAmount. - - Examples: - IOU Converts to the underlying Issue - IOU(10) Returns STAmount of 10 of - the underlying Issue. -*/ +/** + * Converts to IOU Issue or STAmount. + * + * Examples: + * IOU Converts to the underlying Issue + * IOU(10) Returns STAmount of 10 of + * the underlying Issue. + */ class IOU { public: @@ -417,11 +433,12 @@ public: return issue().integral(); } - /** Implicit conversion to Issue or Asset. - - This allows passing an IOU - value where an Issue or Asset is expected. - */ + /** + * Implicit conversion to Issue or Asset. + * + * This allows passing an IOU + * value where an Issue or Asset is expected. + */ operator Issue() const { return issue(); @@ -453,7 +470,9 @@ public: // VFALCO TODO // STAmount operator()(char const* s) const; - /** Returns None-of-Issue */ + /** + * Returns None-of-Issue + */ None operator()(NoneT) const { @@ -472,13 +491,14 @@ operator<<(std::ostream& os, IOU const& iou); //------------------------------------------------------------------------------ -/** Converts to MPT Issue or STAmount. - - Examples: - MPT Converts to the underlying Issue - MPT(10) Returns STAmount of 10 of - the underlying MPT -*/ +/** + * Converts to MPT Issue or STAmount. + * + * Examples: + * MPT Converts to the underlying Issue + * MPT(10) Returns STAmount of 10 of + * the underlying MPT + */ class MPT { public: @@ -504,7 +524,8 @@ public: return issuanceID; } - /** Explicit conversion to MPTIssue or asset. + /** + * Explicit conversion to MPTIssue or asset. */ [[nodiscard]] xrpl::MPTIssue mptIssue() const @@ -522,11 +543,12 @@ public: return true; } - /** Implicit conversion to MPTIssue or asset. - - This allows passing an MPT - value where an MPTIssue is expected. - */ + /** + * Implicit conversion to MPTIssue or asset. + * + * This allows passing an MPT + * value where an MPTIssue is expected. + */ operator xrpl::MPTIssue() const { return mptIssue(); @@ -558,7 +580,9 @@ public: PrettyAmount operator()(detail::EpsilonMultiple) const; - /** Returns None-of-Issue */ + /** + * Returns None-of-Issue + */ None operator()(NoneT) const { @@ -583,7 +607,9 @@ struct AnyT operator()(STAmount const& sta) const; }; -/** Amount specifier with an option for any issuer. */ +/** + * Amount specifier with an option for any issuer. + */ struct AnyAmount { bool isAny; @@ -618,9 +644,10 @@ AnyT::operator()(STAmount const& sta) const return AnyAmount(sta, this); } -/** Returns an amount representing "any issuer" - @note With respect to what the recipient will accept -*/ +/** + * Returns an amount representing "any issuer" + * @note With respect to what the recipient will accept + */ extern AnyT const kAny; } // namespace test::jtx diff --git a/src/test/jtx/balance.h b/src/test/jtx/balance.h index 3f39e9f0fb..9d7937369b 100644 --- a/src/test/jtx/balance.h +++ b/src/test/jtx/balance.h @@ -11,14 +11,15 @@ namespace xrpl::test::jtx { -/** A balance matches. - - This allows "none" which means either the account - doesn't exist (no XRP) or the trust line does not - exist. If an amount is specified, the SLE must - exist even if the amount is 0, or else the test - fails. -*/ +/** + * A balance matches. + * + * This allows "none" which means either the account + * doesn't exist (no XRP) or the trust line does not + * exist. If an amount is specified, the SLE must + * exist even if the amount is 0, or else the test + * fails. + */ class Balance { private: diff --git a/src/test/jtx/batch.h b/src/test/jtx/batch.h index bb1deec1a2..140fc84ab1 100644 --- a/src/test/jtx/batch.h +++ b/src/test/jtx/batch.h @@ -18,7 +18,9 @@ #include #include -/** @brief Helpers for constructing Batch test transactions. */ +/** + * @brief Helpers for constructing Batch test transactions. + */ namespace xrpl::test::jtx::batch { /** @@ -57,7 +59,9 @@ calcConfidentialBatchFee(jtx::Env const& env, uint32_t const& numSigners, uint32 json::Value outer(jtx::Account const& account, uint32_t seq, STAmount const& fee, std::uint32_t flags); -/** @brief Adds an inner Batch transaction to a JTx and autofills it. */ +/** + * @brief Adds an inner Batch transaction to a JTx and autofills it. + */ class Inner { private: @@ -106,7 +110,9 @@ public: } }; -/** @brief Sets the Batch transaction signers on a JTx. */ +/** + * @brief Sets the Batch transaction signers on a JTx. + */ class Sig { public: @@ -129,7 +135,9 @@ public: operator()(Env&, JTx& jt) const; }; -/** @brief Sets a nested multi-signature for a Batch transaction on a JTx. */ +/** + * @brief Sets a nested multi-signature for a Batch transaction on a JTx. + */ class Msig { public: diff --git a/src/test/jtx/check.h b/src/test/jtx/check.h index f66d802247..a5d1f079c4 100644 --- a/src/test/jtx/check.h +++ b/src/test/jtx/check.h @@ -12,14 +12,20 @@ namespace xrpl::test::jtx { -/** Check operations. */ +/** + * Check operations. + */ namespace check { -/** Cash a check requiring that a specific amount be delivered. */ +/** + * Cash a check requiring that a specific amount be delivered. + */ json::Value cash(jtx::Account const& dest, uint256 const& checkId, STAmount const& amount); -/** Type used to specify DeliverMin for cashing a check. */ +/** + * Type used to specify DeliverMin for cashing a check. + */ struct DeliverMin { STAmount value; @@ -28,17 +34,23 @@ struct DeliverMin } }; -/** Cash a check requiring that at least a minimum amount be delivered. */ +/** + * Cash a check requiring that at least a minimum amount be delivered. + */ json::Value cash(jtx::Account const& dest, uint256 const& checkId, DeliverMin const& atLeast); -/** Cancel a check. */ +/** + * Cancel a check. + */ json::Value cancel(jtx::Account const& dest, uint256 const& checkId); } // namespace check -/** Match the number of checks on the account. */ +/** + * Match the number of checks on the account. + */ using checks = OwnerCount; } // namespace xrpl::test::jtx diff --git a/src/test/jtx/delivermin.h b/src/test/jtx/delivermin.h index 29256e37bd..db5014bc07 100644 --- a/src/test/jtx/delivermin.h +++ b/src/test/jtx/delivermin.h @@ -9,7 +9,9 @@ namespace xrpl::test::jtx { -/** Sets the DeliverMin on a JTx. */ +/** + * Sets the DeliverMin on a JTx. + */ class DeliverMin { private: diff --git a/src/test/jtx/deposit.h b/src/test/jtx/deposit.h index 5ba12648e2..3039167cee 100644 --- a/src/test/jtx/deposit.h +++ b/src/test/jtx/deposit.h @@ -10,14 +10,20 @@ #include #include -/** Deposit preauthorize operations */ +/** + * Deposit preauthorize operations + */ namespace xrpl::test::jtx::deposit { -/** Preauthorize for deposit. Invoke as deposit::auth. */ +/** + * Preauthorize for deposit. Invoke as deposit::auth. + */ json::Value auth(Account const& account, Account const& auth); -/** Remove pre-authorization for deposit. Invoke as deposit::unauth. */ +/** + * Remove pre-authorization for deposit. Invoke as deposit::unauth. + */ json::Value unauth(Account const& account, Account const& unauth); diff --git a/src/test/jtx/did.h b/src/test/jtx/did.h index 30c89fd879..da10b476e8 100644 --- a/src/test/jtx/did.h +++ b/src/test/jtx/did.h @@ -10,7 +10,9 @@ #include -/** DID operations. */ +/** + * DID operations. + */ namespace xrpl::test::jtx::did { json::Value @@ -19,7 +21,9 @@ set(jtx::Account const& account); json::Value setValid(jtx::Account const& account); -/** Sets the optional DIDDocument on a DIDSet. */ +/** + * Sets the optional DIDDocument on a DIDSet. + */ class Document { private: @@ -37,7 +41,9 @@ public: } }; -/** Sets the optional URI on a DIDSet. */ +/** + * Sets the optional URI on a DIDSet. + */ class Uri { private: @@ -55,7 +61,9 @@ public: } }; -/** Sets the optional Data on a DIDSet. */ +/** + * Sets the optional Data on a DIDSet. + */ class Data { private: diff --git a/src/test/jtx/directory.h b/src/test/jtx/directory.h index 13473f949e..294cfcc43c 100644 --- a/src/test/jtx/directory.h +++ b/src/test/jtx/directory.h @@ -13,7 +13,9 @@ #include #include -/** Directory operations. */ +/** + * Directory operations. + */ namespace xrpl::test::jtx::directory { enum class Error { @@ -25,14 +27,16 @@ enum class Error { AdjustmentError }; -/// Move the position of the last page in the user's directory on open ledger to -/// newLastPage. Requirements: -/// - directory must have at least two pages (root and one more) -/// - adjust should be used to update owner nodes of the objects affected -/// - newLastPage must be greater than index of the last page in the directory -/// -/// Use this to test tecDIR_FULL errors in open ledger. -/// NOTE: effects will be DISCARDED on env.close() +/** + * Move the position of the last page in the user's directory on open ledger to + * newLastPage. Requirements: + * - directory must have at least two pages (root and one more) + * - adjust should be used to update owner nodes of the objects affected + * - newLastPage must be greater than index of the last page in the directory + * + * Use this to test tecDIR_FULL errors in open ledger. + * NOTE: effects will be DISCARDED on env.close() + */ auto bumpLastPage( Env& env, @@ -40,10 +44,12 @@ bumpLastPage( Keylet directory, std::function adjust) -> std::expected; -/// Implementation of adjust for the most common ledger entry, i.e. one where -/// page index is stored in sfOwnerNode (and only there). Pass this function -/// to bumpLastPage if the last page of directory has only objects -/// of this kind (e.g. ticket, DID, offer, deposit preauth, MPToken etc.) +/** + * Implementation of adjust for the most common ledger entry, i.e. one where + * page index is stored in sfOwnerNode (and only there). Pass this function + * to bumpLastPage if the last page of directory has only objects + * of this kind (e.g. ticket, DID, offer, deposit preauth, MPToken etc.) + */ bool adjustOwnerNode(ApplyView& view, uint256 key, std::uint64_t page); diff --git a/src/test/jtx/domain.h b/src/test/jtx/domain.h index ebcfdb662a..c6061c9795 100644 --- a/src/test/jtx/domain.h +++ b/src/test/jtx/domain.h @@ -7,7 +7,9 @@ namespace xrpl::test::jtx { -/** Set the domain on a JTx. */ +/** + * Set the domain on a JTx. + */ class Domain { private: diff --git a/src/test/jtx/envconfig.h b/src/test/jtx/envconfig.h index 0079605637..1f920fca58 100644 --- a/src/test/jtx/envconfig.h +++ b/src/test/jtx/envconfig.h @@ -17,18 +17,22 @@ getEnvLocalhostAddr() return gEnvUseIPv4 ? "127.0.0.1" : "::1"; } -/// @brief initializes a config object for use with jtx::Env -/// -/// @param config the configuration object to be initialized +/** + * @brief initializes a config object for use with jtx::Env + * + * @param config the configuration object to be initialized + */ extern void setupConfigForUnitTests(Config& config); namespace jtx { -/// @brief creates and initializes a default -/// configuration for jtx::Env -/// -/// @return unique_ptr to Config instance +/** + * @brief creates and initializes a default + * configuration for jtx::Env + * + * @return unique_ptr to Config instance + */ inline std::unique_ptr envconfig() { @@ -37,18 +41,20 @@ envconfig() return p; } -/// @brief creates and initializes a default configuration for jtx::Env and -/// invokes the provided function/lambda with the configuration object. -/// -/// @param modfunc callable function or lambda to modify the default config. -/// The first argument to the function must be std::unique_ptr to -/// xrpl::Config. The function takes ownership of the unique_ptr and -/// relinquishes ownership by returning a unique_ptr. -/// -/// @param args additional arguments that will be passed to -/// the config modifier function (optional) -/// -/// @return unique_ptr to Config instance +/** + * @brief creates and initializes a default configuration for jtx::Env and + * invokes the provided function/lambda with the configuration object. + * + * @param modfunc callable function or lambda to modify the default config. + * The first argument to the function must be std::unique_ptr to + * xrpl::Config. The function takes ownership of the unique_ptr and + * relinquishes ownership by returning a unique_ptr. + * + * @param args additional arguments that will be passed to + * the config modifier function (optional) + * + * @return unique_ptr to Config instance + */ template std::unique_ptr envconfig(F&& modfunc, Args&&... args) @@ -56,14 +62,16 @@ envconfig(F&& modfunc, Args&&... args) return modfunc(envconfig(), std::forward(args)...); } -/// @brief adjust config so no admin ports are enabled -/// -/// this is intended for use with envconfig, as in -/// envconfig(noAdmin) -/// -/// @param cfg config instance to be modified -/// -/// @return unique_ptr to Config instance +/** + * @brief adjust config so no admin ports are enabled + * + * this is intended for use with envconfig, as in + * envconfig(noAdmin) + * + * @param cfg config instance to be modified + * + * @return unique_ptr to Config instance + */ std::unique_ptr noAdmin(std::unique_ptr); std::unique_ptr secureGateway(std::unique_ptr); @@ -74,61 +82,71 @@ std::unique_ptr secureGatewayLocalnet(std::unique_ptr); std::unique_ptr singleThreadIo(std::unique_ptr); -/// @brief adjust configuration with params needed to be a validator -/// -/// this is intended for use with envconfig, as in -/// envconfig(validator, myseed) -/// -/// @param cfg config instance to be modified -/// @param seed seed string for use in secret key generation. A fixed default -/// value will be used if this string is empty -/// -/// @return unique_ptr to Config instance +/** + * @brief adjust configuration with params needed to be a validator + * + * this is intended for use with envconfig, as in + * envconfig(validator, myseed) + * + * @param cfg config instance to be modified + * @param seed seed string for use in secret key generation. A fixed default + * value will be used if this string is empty + * + * @return unique_ptr to Config instance + */ std::unique_ptr validator(std::unique_ptr, std::string const&); -/// @brief add a grpc address and port to config -/// -/// This is intended for use with envconfig, for tests that require a grpc -/// server. If this function is not called, grpc server will not start -/// -/// -/// @param cfg config instance to be modified +/** + * @brief add a grpc address and port to config + * + * This is intended for use with envconfig, for tests that require a grpc + * server. If this function is not called, grpc server will not start + * + * + * @param cfg config instance to be modified + */ std::unique_ptr addGrpcConfig(std::unique_ptr); -/// @brief add a grpc address, port and secureGateway to config -/// -/// This is intended for use with envconfig, for tests that require a grpc -/// server. If this function is not called, grpc server will not start -/// -/// -/// @param cfg config instance to be modified +/** + * @brief add a grpc address, port and secureGateway to config + * + * This is intended for use with envconfig, for tests that require a grpc + * server. If this function is not called, grpc server will not start + * + * + * @param cfg config instance to be modified + */ std::unique_ptr addGrpcConfigWithSecureGateway(std::unique_ptr, std::string const& secureGateway); -/// @brief add a grpc address, port and TLS certificate/key paths to config -/// -/// This is intended for use with envconfig, for tests that require a grpc -/// server with TLS enabled. -/// -/// @param cfg config instance to be modified -/// @param certPath path to SSL certificate file -/// @param keyPath path to SSL private key file +/** + * @brief add a grpc address, port and TLS certificate/key paths to config + * + * This is intended for use with envconfig, for tests that require a grpc + * server with TLS enabled. + * + * @param cfg config instance to be modified + * @param certPath path to SSL certificate file + * @param keyPath path to SSL private key file + */ std::unique_ptr addGrpcConfigWithTLS( std::unique_ptr, std::string const& certPath, std::string const& keyPath); -/// @brief add a grpc address, port and TLS certificate/key/client CA paths to config -/// -/// This is intended for use with envconfig, for tests that require a grpc -/// server with mutual TLS (client certificate verification) enabled. -/// -/// @param cfg config instance to be modified -/// @param certPath path to SSL certificate file -/// @param keyPath path to SSL private key file -/// @param clientCAPath path to SSL client CA certificate file for mTLS +/** + * @brief add a grpc address, port and TLS certificate/key/client CA paths to config + * + * This is intended for use with envconfig, for tests that require a grpc + * server with mutual TLS (client certificate verification) enabled. + * + * @param cfg config instance to be modified + * @param certPath path to SSL certificate file + * @param keyPath path to SSL private key file + * @param clientCAPath path to SSL client CA certificate file for mTLS + */ std::unique_ptr addGrpcConfigWithTLSAndClientCA( std::unique_ptr, @@ -136,15 +154,17 @@ addGrpcConfigWithTLSAndClientCA( std::string const& keyPath, std::string const& clientCAPath); -/// @brief add a grpc address, port and TLS with server cert chain to config -/// -/// This is intended for use with envconfig, for tests that require a grpc -/// server with TLS enabled and intermediate CA certificates. -/// -/// @param cfg config instance to be modified -/// @param certPath path to SSL certificate file -/// @param keyPath path to SSL private key file -/// @param certChainPath path to SSL intermediate CA certificate(s) file +/** + * @brief add a grpc address, port and TLS with server cert chain to config + * + * This is intended for use with envconfig, for tests that require a grpc + * server with TLS enabled and intermediate CA certificates. + * + * @param cfg config instance to be modified + * @param certPath path to SSL certificate file + * @param keyPath path to SSL private key file + * @param certChainPath path to SSL intermediate CA certificate(s) file + */ std::unique_ptr addGrpcConfigWithTLSAndCertChain( std::unique_ptr, diff --git a/src/test/jtx/escrow.h b/src/test/jtx/escrow.h index 728b2e3643..68737c2e35 100644 --- a/src/test/jtx/escrow.h +++ b/src/test/jtx/escrow.h @@ -13,7 +13,9 @@ #include #include -/** Escrow operations. */ +/** + * Escrow operations. + */ namespace xrpl::test::jtx::escrow { json::Value @@ -70,10 +72,14 @@ std::array const kCb3 = { 0x3F, 0xA6, 0x3B, 0x1B, 0x60, 0x6F, 0x2D, 0x26, 0x4A, 0x2D, 0x85, 0x7B, 0xE8, 0xA0, 0x9C, 0x1D, 0xFD, 0x57, 0x0D, 0x15, 0x85, 0x8B, 0xD4, 0x81, 0x01, 0x04}}; -/** Set the "FinishAfter" time tag on a JTx */ +/** + * Set the "FinishAfter" time tag on a JTx + */ auto const kFinishTime = JTxFieldWrapper(sfFinishAfter); -/** Set the "CancelAfter" time tag on a JTx */ +/** + * Set the "CancelAfter" time tag on a JTx + */ auto const kCancelTime = JTxFieldWrapper(sfCancelAfter); auto const kCondition = JTxFieldWrapper(sfCondition); diff --git a/src/test/jtx/fee.h b/src/test/jtx/fee.h index ad3002dc75..3754036479 100644 --- a/src/test/jtx/fee.h +++ b/src/test/jtx/fee.h @@ -13,7 +13,9 @@ namespace xrpl::test::jtx { -/** Set the fee on a JTx. */ +/** + * Set the fee on a JTx. + */ class Fee { private: diff --git a/src/test/jtx/flags.h b/src/test/jtx/flags.h index 83470f9a1b..5bbe3c8d12 100644 --- a/src/test/jtx/flags.h +++ b/src/test/jtx/flags.h @@ -97,18 +97,24 @@ namespace test::jtx { // JSON generators -/** Add and/or remove flag. */ +/** + * Add and/or remove flag. + */ json::Value fset(Account const& account, std::uint32_t on, std::uint32_t off = 0); -/** Remove account flag. */ +/** + * Remove account flag. + */ inline json::Value fclear(Account const& account, std::uint32_t off) { return fset(account, 0, off); } -/** Match set account flags */ +/** + * Match set account flags + */ class Flags : private xrpl::detail::FlagsHelper { private: @@ -124,7 +130,9 @@ public: operator()(Env& env) const; }; -/** Match clear account flags */ +/** + * Match clear account flags + */ class Nflags : private xrpl::detail::FlagsHelper { private: diff --git a/src/test/jtx/impl/AMMTest.cpp b/src/test/jtx/impl/AMMTest.cpp index 46c5e44bd3..3b4aae20e2 100644 --- a/src/test/jtx/impl/AMMTest.cpp +++ b/src/test/jtx/impl/AMMTest.cpp @@ -197,7 +197,7 @@ AMMTestBase::testAMM(std::function const& cb, TestAM XRPAmount AMMTest::reserve(jtx::Env& env, std::uint32_t count) { - return env.current()->fees().accountReserve(count); + return env.current()->fees().accountReserve(count, 1); } XRPAmount diff --git a/src/test/jtx/impl/Env.cpp b/src/test/jtx/impl/Env.cpp index db45deb6de..3f6aca9fcb 100644 --- a/src/test/jtx/impl/Env.cpp +++ b/src/test/jtx/impl/Env.cpp @@ -271,6 +271,33 @@ Env::ownerCount(Account const& account) const return sle->getFieldU32(sfOwnerCount); } +std::uint32_t +Env::sponsoredOwnerCount(Account const& account) const +{ + auto const sle = le(account); + if (!sle) + Throw("missing account root"); + return sle->getFieldU32(sfSponsoredOwnerCount); +} + +std::uint32_t +Env::sponsoringOwnerCount(Account const& account) const +{ + auto const sle = le(account); + if (!sle) + Throw("missing account root"); + return sle->getFieldU32(sfSponsoringOwnerCount); +} + +std::uint32_t +Env::sponsoringAccountCount(Account const& account) const +{ + auto const sle = le(account); + if (!sle) + Throw("missing account root"); + return sle->getFieldU32(sfSponsoringAccountCount); +} + std::uint32_t Env::seq(Account const& account) const { diff --git a/src/test/jtx/impl/TestHelpers.cpp b/src/test/jtx/impl/TestHelpers.cpp index 95c8a68436..4d3869b4f9 100644 --- a/src/test/jtx/impl/TestHelpers.cpp +++ b/src/test/jtx/impl/TestHelpers.cpp @@ -94,6 +94,24 @@ ownerCount(Env const& env, Account const& account) return env.ownerCount(account); } +std::uint32_t +sponsoredOwnerCount(Env const& env, Account const& account) +{ + return env.sponsoredOwnerCount(account); +} + +std::uint32_t +sponsoringOwnerCount(Env const& env, Account const& account) +{ + return env.sponsoringOwnerCount(account); +} + +std::uint32_t +sponsoringAccountCount(Env const& env, Account const& account) +{ + return env.sponsoringAccountCount(account); +} + /* Path finding */ /******************************************************************************/ void diff --git a/src/test/jtx/impl/dids.cpp b/src/test/jtx/impl/dids.cpp index d82250ca34..9c27ea21e3 100644 --- a/src/test/jtx/impl/dids.cpp +++ b/src/test/jtx/impl/dids.cpp @@ -7,7 +7,9 @@ #include #include -/** DID operations. */ +/** + * DID operations. + */ namespace xrpl::test::jtx::did { json::Value diff --git a/src/test/jtx/impl/directory.cpp b/src/test/jtx/impl/directory.cpp index 9fb06e25df..5eaf0be196 100644 --- a/src/test/jtx/impl/directory.cpp +++ b/src/test/jtx/impl/directory.cpp @@ -18,7 +18,9 @@ #include #include -/** Directory operations. */ +/** + * Directory operations. + */ namespace xrpl::test::jtx::directory { auto diff --git a/src/test/jtx/impl/escrow.cpp b/src/test/jtx/impl/escrow.cpp index 007f492849..61c260a5d0 100644 --- a/src/test/jtx/impl/escrow.cpp +++ b/src/test/jtx/impl/escrow.cpp @@ -14,7 +14,9 @@ #include -/** Escrow operations. */ +/** + * Escrow operations. + */ namespace xrpl::test::jtx::escrow { json::Value diff --git a/src/test/jtx/impl/multisign.cpp b/src/test/jtx/impl/multisign.cpp index 66c5a4f27d..d948042bda 100644 --- a/src/test/jtx/impl/multisign.cpp +++ b/src/test/jtx/impl/multisign.cpp @@ -64,7 +64,8 @@ Msig::operator()(Env& env, JTx& jt) const { auto const mySigners = signers; auto callback = [subField = subField, mySigners, &env](Env&, JTx& jtx) { - // Where to put the signature. Supports sfCounterPartySignature. + // Where to put the signature. Supports sfCounterPartySignature and + // sfSponsorSignature. auto& sigObject = subField ? jtx[*subField] : jtx.jv; // The signing pub key is only required at the top level. diff --git a/src/test/jtx/impl/owners.cpp b/src/test/jtx/impl/owners.cpp index 2ff93757f0..ed10ca6930 100644 --- a/src/test/jtx/impl/owners.cpp +++ b/src/test/jtx/impl/owners.cpp @@ -44,6 +44,24 @@ Owners::operator()(Env& env) const env.test.expect(env.le(account_)->getFieldU32(sfOwnerCount) == value_); } +void +SponsoredOwners::operator()(Env& env) const +{ + env.test.expect(env.le(account_)->getFieldU32(sfSponsoredOwnerCount) == value_); +} + +void +SponsoringOwners::operator()(Env& env) const +{ + env.test.expect(env.le(account_)->getFieldU32(sfSponsoringOwnerCount) == value_); +} + +void +SponsoringAccountCount::operator()(Env& env) const +{ + env.test.expect(env.le(account_)->getFieldU32(sfSponsoringAccountCount) == value_); +} + } // namespace test::jtx } // namespace xrpl diff --git a/src/test/jtx/impl/sig.cpp b/src/test/jtx/impl/sig.cpp index 7140fad3de..e0123073b1 100644 --- a/src/test/jtx/impl/sig.cpp +++ b/src/test/jtx/impl/sig.cpp @@ -18,7 +18,7 @@ Sig::operator()(Env&, JTx& jt) const // VFALCO Inefficient pre-C++14 auto const account = *account_; auto callback = [subField = subField_, account](Env&, JTx& jtx) { - // Where to put the signature. Supports sfCounterPartySignature. + // Where to put the signature. Supports sfCounterPartySignature and sfSponsorSignature. auto& sigObject = subField ? jtx[*subField] : jtx.jv; jtx::sign(jtx.jv, account, sigObject); diff --git a/src/test/jtx/impl/sponsor.cpp b/src/test/jtx/impl/sponsor.cpp new file mode 100644 index 0000000000..cdf68800f5 --- /dev/null +++ b/src/test/jtx/impl/sponsor.cpp @@ -0,0 +1,98 @@ +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl::test::jtx::sponsor { + +json::Value +set(jtx::Account const& account, + uint32_t flags, + std::optional const reserveCount, + std::optional const feeAmount, + std::optional const maxFee) +{ + json::Value jv; + jv[jss::TransactionType] = jss::SponsorshipSet; + jv[jss::Account] = account.human(); + jv[sfFlags.jsonName] = flags; + if (reserveCount) + jv[sfRemainingOwnerCount.jsonName] = *reserveCount; + if (feeAmount) + jv[sfFeeAmount.jsonName] = feeAmount->getJson(JsonOptions::Values::None); + if (maxFee) + jv[sfMaxFee.jsonName] = maxFee->getJson(JsonOptions::Values::None); + return jv; +} + +json::Value +del(jtx::Account const& account) +{ + json::Value jv; + jv[jss::TransactionType] = jss::SponsorshipSet; + jv[jss::Account] = account.human(); + jv[sfFlags.jsonName] = tfDeleteObject; + return jv; +} + +json::Value +transfer(jtx::Account const& account, uint32_t flags, std::optional const& index) +{ + json::Value jv; + jv[jss::TransactionType] = jss::SponsorshipTransfer; + jv[jss::Account] = account.human(); + jv[sfFlags.jsonName] = flags; + if (index) + jv[sfObjectID.jsonName] = to_string(*index); + return jv; +} + +void +CounterpartySponsor::operator()(Env& env, JTx& jt) const +{ + jt.jv[sfCounterpartySponsor.jsonName] = sponsor_.human(); +} + +void +SponseeAcc::operator()(Env& env, JTx& jt) const +{ + jt.jv[sfSponsee.jsonName] = sponsee_.human(); +} + +void +As::operator()(Env& env, JTx& jt) const +{ + jt.jv[sfSponsor.jsonName] = sponsor_.human(); + jt.jv[sfSponsorFlags.jsonName] = flags_; +} + +json::Value +ledgerEntry(jtx::Env& env, jtx::Account const& sponsor, jtx::Account const& sponsee) +{ + json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::sponsorship][jss::sponsor] = sponsor.human(); + jvParams[jss::sponsorship][jss::sponsee] = sponsee.human(); + return env.rpc("json", "ledger_entry", to_string(jvParams)); +} + +STAmount +sponsorshipFeeBalance(jtx::Env& env, jtx::Account const& sponsor, jtx::Account const& sponsee) +{ + return env.le(keylet::sponsorship(sponsor, sponsee))->getFieldAmount(sfFeeAmount).xrp(); +} + +} // namespace xrpl::test::jtx::sponsor diff --git a/src/test/jtx/jtx_json.h b/src/test/jtx/jtx_json.h index b22d4c30e1..1ef5c155a8 100644 --- a/src/test/jtx/jtx_json.h +++ b/src/test/jtx/jtx_json.h @@ -9,7 +9,9 @@ namespace xrpl::test::jtx { -/** Inject raw JSON. */ +/** + * Inject raw JSON. + */ class Json { private: diff --git a/src/test/jtx/ledgerStateFix.h b/src/test/jtx/ledgerStateFix.h index dd1ac19f04..2fe5c8accc 100644 --- a/src/test/jtx/ledgerStateFix.h +++ b/src/test/jtx/ledgerStateFix.h @@ -5,14 +5,20 @@ #include #include -/** LedgerStateFix operations. */ +/** + * LedgerStateFix operations. + */ namespace xrpl::test::jtx::ledgerStateFix { -/** Repair the links in an NFToken directory. */ +/** + * Repair the links in an NFToken directory. + */ json::Value nftPageLinks(jtx::Account const& acct, jtx::Account const& owner); -/** Repair sfExchangeRate on a book directory's first page. */ +/** + * Repair sfExchangeRate on a book directory's first page. + */ json::Value bookExchangeRate(jtx::Account const& acct, uint256 const& bookDir); diff --git a/src/test/jtx/memo.h b/src/test/jtx/memo.h index cea4923d38..4e342816ca 100644 --- a/src/test/jtx/memo.h +++ b/src/test/jtx/memo.h @@ -8,11 +8,12 @@ namespace xrpl::test::jtx { -/** Add a memo to a JTx. - - If a memo already exists, the new - memo is appended to the array. -*/ +/** + * Add a memo to a JTx. + * + * If a memo already exists, the new + * memo is appended to the array. + */ class Memo { private: diff --git a/src/test/jtx/mpt.h b/src/test/jtx/mpt.h index 1a7fe94785..c6532ab14a 100644 --- a/src/test/jtx/mpt.h +++ b/src/test/jtx/mpt.h @@ -65,7 +65,9 @@ gMakeZeroBuffer(std::size_t size) return b; } -/** @brief Test helper that checks MPT flag settings after creation. */ +/** + * @brief Test helper that checks MPT flag settings after creation. + */ class MptFlags { private: @@ -86,7 +88,9 @@ public: operator()(Env& env) const; }; -/** @brief Test helper that checks MPT issuance or holder balances. */ +/** + * @brief Test helper that checks MPT issuance or holder balances. + */ class MptBalance { private: @@ -104,7 +108,9 @@ public: operator()(Env& env) const; }; -/** @brief Test helper that accepts any condition supplied by a callback. */ +/** + * @brief Test helper that accepts any condition supplied by a callback. + */ class RequireAny { private: @@ -121,7 +127,9 @@ public: using Holders = std::vector; -/** @brief Arguments for building an MPTokenIssuanceCreate test transaction. */ +/** + * @brief Arguments for building an MPTokenIssuanceCreate test transaction. + */ struct MPTCreate { static inline std::vector allHolders = {}; @@ -145,7 +153,9 @@ struct MPTCreate std::optional err = std::nullopt; }; -/** @brief Arguments for initializing funded MPT test accounts and issuance. */ +/** + * @brief Arguments for initializing funded MPT test accounts and issuance. + */ struct MPTInit { // Default-initialized so designated-initializer call sites that omit @@ -161,7 +171,9 @@ struct MPTInit }; static MPTInit const kMptInitNoFund{.fund = false}; -/** @brief Full constructor arguments for MPTTester initialization. */ +/** + * @brief Full constructor arguments for MPTTester initialization. + */ struct MPTInitDef { Env& env; @@ -179,7 +191,9 @@ struct MPTInitDef std::optional err = std::nullopt; }; -/** @brief Arguments for building an MPTokenIssuanceDestroy test transaction. */ +/** + * @brief Arguments for building an MPTokenIssuanceDestroy test transaction. + */ struct MPTDestroy { std::optional issuer = std::nullopt; @@ -190,7 +204,9 @@ struct MPTDestroy std::optional err = std::nullopt; }; -/** @brief Arguments for building an MPTokenAuthorize test transaction. */ +/** + * @brief Arguments for building an MPTokenAuthorize test transaction. + */ struct MPTAuthorize { std::optional account = std::nullopt; @@ -202,7 +218,9 @@ struct MPTAuthorize std::optional err = std::nullopt; }; -/** @brief Arguments for building an MPTokenIssuanceSet test transaction. */ +/** + * @brief Arguments for building an MPTokenIssuanceSet test transaction. + */ struct MPTSet { std::optional account = std::nullopt; @@ -222,7 +240,9 @@ struct MPTSet std::optional err = std::nullopt; }; -/** @brief Arguments for building a ConfidentialMPTConvert test transaction. */ +/** + * @brief Arguments for building a ConfidentialMPTConvert test transaction. + */ struct MPTConvert { std::optional account = std::nullopt; @@ -250,7 +270,9 @@ struct MPTConvert std::optional err = std::nullopt; }; -/** @brief Arguments for building a ConfidentialMPTMergeInbox test transaction. */ +/** + * @brief Arguments for building a ConfidentialMPTMergeInbox test transaction. + */ struct MPTMergeInbox { std::optional account = std::nullopt; @@ -264,7 +286,9 @@ struct MPTMergeInbox std::optional err = std::nullopt; }; -/** @brief Arguments for building a ConfidentialMPTSend test transaction. */ +/** + * @brief Arguments for building a ConfidentialMPTSend test transaction. + */ struct MPTConfidentialSend { std::optional account = std::nullopt; @@ -293,7 +317,9 @@ struct MPTConfidentialSend std::optional err = std::nullopt; }; -/** @brief Arguments for building a ConfidentialMPTConvertBack test transaction. */ +/** + * @brief Arguments for building a ConfidentialMPTConvertBack test transaction. + */ struct MPTConvertBack { std::optional account = std::nullopt; @@ -316,7 +342,9 @@ struct MPTConvertBack std::optional err = std::nullopt; }; -/** @brief Arguments for building a ConfidentialMPTClawback test transaction. */ +/** + * @brief Arguments for building a ConfidentialMPTClawback test transaction. + */ struct MPTConfidentialClawback { std::optional account = std::nullopt; @@ -339,16 +367,24 @@ struct MPTConfidentialClawback */ struct PedersenProofParams { - /** @brief The Pedersen commitment used by the proof. */ + /** + * @brief The Pedersen commitment used by the proof. + */ Buffer const pedersenCommitment; - /** @brief Either the spending balance or the value being transferred. */ + /** + * @brief Either the spending balance or the value being transferred. + */ uint64_t const amt; - /** @brief The encrypted amount linked to the Pedersen commitment. */ + /** + * @brief The encrypted amount linked to the Pedersen commitment. + */ Buffer const encryptedAmt; - /** @brief The blinding factor used to create the Pedersen commitment. */ + /** + * @brief The blinding factor used to create the Pedersen commitment. + */ Buffer const blindingFactor; }; @@ -365,13 +401,19 @@ struct PedersenProofParams */ struct ConfidentialSendChainState { - /** @brief Decrypted spending balance after the previous send. */ + /** + * @brief Decrypted spending balance after the previous send. + */ std::uint64_t spending; - /** @brief Encrypted spending balance after the previous send. */ + /** + * @brief Encrypted spending balance after the previous send. + */ Buffer encSpending; - /** @brief sfConfidentialBalanceVersion after the previous send. */ + /** + * @brief sfConfidentialBalanceVersion after the previous send. + */ std::uint32_t version; }; diff --git a/src/test/jtx/multisign.h b/src/test/jtx/multisign.h index c90e28537a..65e39f971d 100644 --- a/src/test/jtx/multisign.h +++ b/src/test/jtx/multisign.h @@ -20,7 +20,9 @@ namespace xrpl::test::jtx { -/** A signer in a SignerList */ +/** + * A signer in a SignerList + */ struct Signer { std::uint32_t weight; @@ -36,24 +38,31 @@ struct Signer json::Value signers(Account const& account, std::uint32_t quorum, std::vector const& v); -/** Remove a signer list. */ +/** + * Remove a signer list. + */ json::Value signers(Account const& account, NoneT); //------------------------------------------------------------------------------ -/** Set a multisignature on a JTx. */ +/** + * Set a multisignature on a JTx. + */ class Msig { public: std::vector signers; - /** Alternative transaction object field in which to place the signer list. + /** + * Alternative transaction object field in which to place the signer list. * * subField is only supported if an account_ is provided as well. */ SField const* const subField = nullptr; - /// Used solely as a convenience placeholder for ctors that do _not_ specify - /// a subfield. + /** + * Used solely as a convenience placeholder for ctors that do _not_ specify + * a subfield. + */ static constexpr SField const* kTopLevel = nullptr; Msig(SField const* subField, std::vector signers) @@ -103,7 +112,9 @@ public: //------------------------------------------------------------------------------ -/** The number of signer lists matches. */ +/** + * The number of signer lists matches. + */ using siglists = OwnerCount; } // namespace xrpl::test::jtx diff --git a/src/test/jtx/noop.h b/src/test/jtx/noop.h index c38dfdde25..2ca97a1916 100644 --- a/src/test/jtx/noop.h +++ b/src/test/jtx/noop.h @@ -7,7 +7,9 @@ namespace xrpl::test::jtx { -/** The null transaction. */ +/** + * The null transaction. + */ inline json::Value noop(Account const& account) { diff --git a/src/test/jtx/offer.h b/src/test/jtx/offer.h index f3e7277933..140e16da83 100644 --- a/src/test/jtx/offer.h +++ b/src/test/jtx/offer.h @@ -9,7 +9,9 @@ namespace xrpl::test::jtx { -/** Create an offer. */ +/** + * Create an offer. + */ json::Value offer( Account const& account, @@ -17,7 +19,9 @@ offer( STAmount const& takerGets, std::uint32_t flags = 0); -/** Cancel an offer. */ +/** + * Cancel an offer. + */ json::Value offerCancel(Account const& account, std::uint32_t offerSeq); diff --git a/src/test/jtx/owners.h b/src/test/jtx/owners.h index 0fb38b407c..1e12603429 100644 --- a/src/test/jtx/owners.h +++ b/src/test/jtx/owners.h @@ -48,7 +48,9 @@ public: } }; -/** Match the number of items in the account's owner directory */ +/** + * Match the number of items in the account's owner directory + */ class Owners { private: @@ -64,13 +66,79 @@ public: operator()(Env& env) const; }; -/** Match the number of trust lines in the account's owner directory */ +/** + * Match the account's SponsoredOwnerCount field: the number of owned + * objects whose reserve is sponsored by another account. + */ +class SponsoredOwners +{ +private: + Account account_; + std::uint32_t value_; + +public: + SponsoredOwners(Account account, std::uint32_t value) + : account_(std::move(account)), value_(value) + { + } + + void + operator()(Env& env) const; +}; + +/** + * Match the account's SponsoringOwnerCount field: the number of objects + * (owned by other accounts) whose reserve this account sponsors. + */ +class SponsoringOwners +{ +private: + Account account_; + std::uint32_t value_; + +public: + SponsoringOwners(Account account, std::uint32_t value) + : account_(std::move(account)), value_(value) + { + } + + void + operator()(Env& env) const; +}; + +/** + * Match the account's SponsoringAccountCount field: the number of accounts + * whose base reserve this account sponsors. + */ +class SponsoringAccountCount +{ +private: + Account account_; + std::uint32_t value_; + +public: + SponsoringAccountCount(Account account, std::uint32_t value) + : account_(std::move(account)), value_(value) + { + } + + void + operator()(Env& env) const; +}; + +/** + * Match the number of trust lines in the account's owner directory + */ using lines = OwnerCount; -/** Match the number of offers in the account's owner directory */ +/** + * Match the number of offers in the account's owner directory + */ using offers = OwnerCount; -/** Match the number of MPToken in the account's owner directory */ +/** + * Match the number of MPToken in the account's owner directory + */ using mptokens = OwnerCount; } // namespace test::jtx diff --git a/src/test/jtx/paths.h b/src/test/jtx/paths.h index b141300a66..07d0117f8f 100644 --- a/src/test/jtx/paths.h +++ b/src/test/jtx/paths.h @@ -16,7 +16,9 @@ class STPath; namespace test::jtx { -/** Set Paths, SendMax on a JTx. */ +/** + * Set Paths, SendMax on a JTx. + */ class Paths { private: @@ -36,10 +38,11 @@ public: //------------------------------------------------------------------------------ -/** Add a path. - - If no paths are present, a new one is created. -*/ +/** + * Add a path. + * + * If no paths are present, a new one is created. + */ class Path { private: diff --git a/src/test/jtx/pay.h b/src/test/jtx/pay.h index fa193e7b02..04ccf27f70 100644 --- a/src/test/jtx/pay.h +++ b/src/test/jtx/pay.h @@ -8,7 +8,9 @@ namespace xrpl::test::jtx { -/** Create a payment. */ +/** + * Create a payment. + */ json::Value pay(AccountID const& account, AccountID const& to, AnyAmount amount); json::Value diff --git a/src/test/jtx/prop.h b/src/test/jtx/prop.h index e85751e4c9..24dca21ca0 100644 --- a/src/test/jtx/prop.h +++ b/src/test/jtx/prop.h @@ -8,7 +8,9 @@ namespace xrpl::test::jtx { -/** Set a property on a JTx. */ +/** + * Set a property on a JTx. + */ template struct Prop { diff --git a/src/test/jtx/quality.h b/src/test/jtx/quality.h index c7896fadf9..a15d319b40 100644 --- a/src/test/jtx/quality.h +++ b/src/test/jtx/quality.h @@ -7,7 +7,9 @@ namespace xrpl::test::jtx { -/** Sets the literal QualityIn on a trust JTx. */ +/** + * Sets the literal QualityIn on a trust JTx. + */ class QualityIn { private: @@ -22,7 +24,9 @@ public: operator()(Env&, JTx& jtx) const; }; -/** Sets the QualityIn on a trust JTx. */ +/** + * Sets the QualityIn on a trust JTx. + */ class QualityInPercent { private: @@ -35,7 +39,9 @@ public: operator()(Env&, JTx& jtx) const; }; -/** Sets the literal QualityOut on a trust JTx. */ +/** + * Sets the literal QualityOut on a trust JTx. + */ class QualityOut { private: @@ -50,7 +56,9 @@ public: operator()(Env&, JTx& jtx) const; }; -/** Sets the QualityOut on a trust JTx as a percentage. */ +/** + * Sets the QualityOut on a trust JTx as a percentage. + */ class QualityOutPercent { private: diff --git a/src/test/jtx/rate.h b/src/test/jtx/rate.h index f76c2dd538..f8caf39fd8 100644 --- a/src/test/jtx/rate.h +++ b/src/test/jtx/rate.h @@ -6,7 +6,9 @@ namespace xrpl::test::jtx { -/** Set a transfer rate. */ +/** + * Set a transfer rate. + */ json::Value rate(Account const& account, double multiplier); diff --git a/src/test/jtx/regkey.h b/src/test/jtx/regkey.h index 676633b2b7..8aa5bd9709 100644 --- a/src/test/jtx/regkey.h +++ b/src/test/jtx/regkey.h @@ -7,11 +7,15 @@ namespace xrpl::test::jtx { -/** Disable the regular key. */ +/** + * Disable the regular key. + */ json::Value regkey(Account const& account, DisabledT); -/** Set a regular key. */ +/** + * Set a regular key. + */ json::Value regkey(Account const& account, Account const& signer); diff --git a/src/test/jtx/require.h b/src/test/jtx/require.h index 19e161b938..eba20b613c 100644 --- a/src/test/jtx/require.h +++ b/src/test/jtx/require.h @@ -23,7 +23,9 @@ requireArgs(test::jtx::requires_t& vec, Cond const& cond, Args const&... args) namespace test::jtx { -/** Compose many condition functors into one */ +/** + * Compose many condition functors into one + */ template require_t required(Args const&... args) @@ -36,12 +38,13 @@ required(Args const&... args) }; } -/** Check a set of conditions. - - The conditions are checked after a JTx is - applied, and only if the resulting TER - matches the expected TER. -*/ +/** + * Check a set of conditions. + * + * The conditions are checked after a JTx is + * applied, and only if the resulting TER + * matches the expected TER. + */ class Require { private: diff --git a/src/test/jtx/rpc.h b/src/test/jtx/rpc.h index bc05450909..9bd99c15f8 100644 --- a/src/test/jtx/rpc.h +++ b/src/test/jtx/rpc.h @@ -12,9 +12,10 @@ namespace xrpl::test::jtx { -/** Set the expected result code for a JTx - The test will fail if the code doesn't match. -*/ +/** + * Set the expected result code for a JTx + * The test will fail if the code doesn't match. + */ class Rpc { private: @@ -24,13 +25,17 @@ private: std::optional errorException_; public: - /// If there's an error code, we expect an error message + /** + * If there's an error code, we expect an error message + */ explicit Rpc(ErrorCodeI code, std::optional m = {}) : code_(code), errorMessage_(std::move(m)) { } - /// If there is not a code, we expect an exception message + /** + * If there is not a code, we expect an exception message + */ explicit Rpc(std::string error, std::optional exceptionMessage = {}) : error_(error), errorException_(std::move(exceptionMessage)) { diff --git a/src/test/jtx/sendmax.h b/src/test/jtx/sendmax.h index 1241d76b91..672959d1aa 100644 --- a/src/test/jtx/sendmax.h +++ b/src/test/jtx/sendmax.h @@ -9,7 +9,9 @@ namespace xrpl::test::jtx { -/** Sets the SendMax on a JTx. */ +/** + * Sets the SendMax on a JTx. + */ class Sendmax { private: diff --git a/src/test/jtx/seq.h b/src/test/jtx/seq.h index 956c0a77e8..323fb33bb2 100644 --- a/src/test/jtx/seq.h +++ b/src/test/jtx/seq.h @@ -9,7 +9,9 @@ namespace xrpl::test::jtx { -/** Set the sequence number on a JTx. */ +/** + * Set the sequence number on a JTx. + */ struct Seq { private: diff --git a/src/test/jtx/sig.h b/src/test/jtx/sig.h index 76f0a34dff..1f20dcf942 100644 --- a/src/test/jtx/sig.h +++ b/src/test/jtx/sig.h @@ -11,26 +11,31 @@ namespace xrpl::test::jtx { -/** Set the regular signature on a JTx. - @note For multisign, use msig. -*/ +/** + * Set the regular signature on a JTx. + * @note For multisign, use msig. + */ class Sig { private: bool manual_ = true; - /** Alternative transaction object field in which to place the signature. + /** + * Alternative transaction object field in which to place the signature. * * subField is only supported if an account_ is provided as well. */ SField const* const subField_ = nullptr; - /** Account that will generate the signature. + /** + * Account that will generate the signature. * * If not provided, no signature will be added by this helper. See also * Env::autofillSig. */ std::optional account_; - /// Used solely as a convenience placeholder for ctors that do _not_ specify - /// a subfield. + /** + * Used solely as a convenience placeholder for ctors that do _not_ specify + * a subfield. + */ static constexpr SField const* kTopLevel = nullptr; public: diff --git a/src/test/jtx/sponsor.h b/src/test/jtx/sponsor.h new file mode 100644 index 0000000000..43d55d7246 --- /dev/null +++ b/src/test/jtx/sponsor.h @@ -0,0 +1,105 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace xrpl::test::jtx::sponsor { + +json::Value +set(jtx::Account const& account, + std::uint32_t flags, + std::optional const reserveCount = std::nullopt, + std::optional const feeAmount = std::nullopt, + std::optional const maxFee = std::nullopt); + +inline json::Value +set_fee( + jtx::Account const& account, + std::uint32_t flags, + STAmount feeAmount, + std::optional maxFee = std::nullopt) +{ + return set(account, flags, std::nullopt, std::move(feeAmount), std::move(maxFee)); +} + +inline json::Value +set_reserve(jtx::Account const& account, std::uint32_t flags, std::uint32_t reserveCount) +{ + return set(account, flags, reserveCount); +} + +inline json::Value +set_max_fee(jtx::Account const& account, std::uint32_t flags, STAmount maxFee) +{ + return set(account, flags, std::nullopt, std::nullopt, std::move(maxFee)); +} + +json::Value +del(jtx::Account const& account); + +json::Value +transfer( + jtx::Account const& account, + uint32_t flags, + std::optional const& index = std::nullopt); + +struct CounterpartySponsor +{ +private: + jtx::Account sponsor_; + +public: + CounterpartySponsor(jtx::Account account) : sponsor_(std::move(account)) + { + } + + void + operator()(jtx::Env&, jtx::JTx& jtx) const; +}; + +struct SponseeAcc +{ +private: + jtx::Account sponsee_; + +public: + SponseeAcc(jtx::Account account) : sponsee_(std::move(account)) + { + } + + void + operator()(jtx::Env&, jtx::JTx& jtx) const; +}; + +struct As +{ +private: + jtx::Account sponsor_; + std::uint32_t flags_; + +public: + As(jtx::Account account, std::uint32_t flags = 0) : sponsor_(std::move(account)), flags_(flags) + { + } + + void + operator()(jtx::Env&, jtx::JTx& jtx) const; +}; + +json::Value +ledgerEntry(jtx::Env& env, jtx::Account const& sponsor, jtx::Account const& sponsee); + +STAmount +sponsorshipFeeBalance(jtx::Env& env, jtx::Account const& sponsor, jtx::Account const& sponsee); + +} // namespace xrpl::test::jtx::sponsor diff --git a/src/test/jtx/tag.h b/src/test/jtx/tag.h index 77870367a9..b9a10e55f4 100644 --- a/src/test/jtx/tag.h +++ b/src/test/jtx/tag.h @@ -7,7 +7,9 @@ namespace xrpl::test::jtx { -/** Set the destination tag on a JTx*/ +/** + * Set the destination tag on a JTx + */ struct Dtag { private: @@ -22,7 +24,9 @@ public: operator()(Env&, JTx& jt) const; }; -/** Set the source tag on a JTx*/ +/** + * Set the source tag on a JTx + */ struct Stag { private: diff --git a/src/test/jtx/tags.h b/src/test/jtx/tags.h index 4f4afbd1fa..1915c57b5c 100644 --- a/src/test/jtx/tags.h +++ b/src/test/jtx/tags.h @@ -20,7 +20,9 @@ struct DisabledT }; static DisabledT const kDisabled; -/** Used for Fee() calls that use an owner reserve increment */ +/** + * Used for Fee() calls that use an owner reserve increment + */ struct IncrementT { IncrementT() = default; diff --git a/src/test/jtx/ter.h b/src/test/jtx/ter.h index 880711dca0..0ac65e62ee 100644 --- a/src/test/jtx/ter.h +++ b/src/test/jtx/ter.h @@ -10,9 +10,10 @@ namespace xrpl::test::jtx { -/** Set the expected result code for a JTx - The test will fail if the code doesn't match. -*/ +/** + * Set the expected result code for a JTx + * The test will fail if the code doesn't match. + */ class Ter { private: diff --git a/src/test/jtx/ticket.h b/src/test/jtx/ticket.h index 1035be7674..9c9a698eaa 100644 --- a/src/test/jtx/ticket.h +++ b/src/test/jtx/ticket.h @@ -18,14 +18,20 @@ namespace xrpl::test::jtx { without changing the base declarations. */ -/** Ticket operations */ +/** + * Ticket operations + */ namespace ticket { -/** Create one of more tickets */ +/** + * Create one of more tickets + */ json::Value create(Account const& account, std::uint32_t count); -/** Set a ticket sequence on a JTx. */ +/** + * Set a ticket sequence on a JTx. + */ class Use { private: @@ -42,7 +48,9 @@ public: } // namespace ticket -/** Match the number of tickets on the account. */ +/** + * Match the number of tickets on the account. + */ using tickets = OwnerCount; } // namespace xrpl::test::jtx diff --git a/src/test/jtx/token.h b/src/test/jtx/token.h index 97f968fdfc..e94cfd2e12 100644 --- a/src/test/jtx/token.h +++ b/src/test/jtx/token.h @@ -16,11 +16,15 @@ namespace xrpl::test::jtx::token { -/** Mint an NFToken. */ +/** + * Mint an NFToken. + */ json::Value mint(jtx::Account const& account, std::uint32_t tokenTaxon = 0); -/** Sets the optional TransferFee on an NFTokenMint. */ +/** + * Sets the optional TransferFee on an NFTokenMint. + */ class XferFee { private: @@ -35,7 +39,9 @@ public: operator()(Env&, JTx& jtx) const; }; -/** Sets the optional Issuer on an NFTokenMint. */ +/** + * Sets the optional Issuer on an NFTokenMint. + */ class Issuer { private: @@ -50,7 +56,9 @@ public: operator()(Env&, JTx& jtx) const; }; -/** Sets the optional URI on an NFTokenMint. */ +/** + * Sets the optional URI on an NFTokenMint. + */ class Uri { private: @@ -65,7 +73,9 @@ public: operator()(Env&, JTx& jtx) const; }; -/** Sets the optional amount field on an NFTokenMint. */ +/** + * Sets the optional amount field on an NFTokenMint. + */ class Amount { private: @@ -80,7 +90,9 @@ public: operator()(Env&, JTx& jtx) const; }; -/** Get the next NFTokenID that will be issued. */ +/** + * Get the next NFTokenID that will be issued. + */ uint256 getNextID( jtx::Env const& env, @@ -89,7 +101,9 @@ getNextID( std::uint16_t flags = 0, std::uint16_t xferFee = 0); -/** Get the NFTokenID for a particular nftSequence. */ +/** + * Get the NFTokenID for a particular nftSequence. + */ uint256 getID( jtx::Env const& env, @@ -99,15 +113,21 @@ getID( std::uint16_t flags = 0, std::uint16_t xferFee = 0); -/** Burn an NFToken. */ +/** + * Burn an NFToken. + */ json::Value burn(jtx::Account const& account, uint256 const& nftokenID); -/** Create an NFTokenOffer. */ +/** + * Create an NFTokenOffer. + */ json::Value createOffer(jtx::Account const& account, uint256 const& nftokenID, STAmount const& amount); -/** Sets the optional Owner on an NFTokenOffer. */ +/** + * Sets the optional Owner on an NFTokenOffer. + */ class Owner { private: @@ -122,7 +142,9 @@ public: operator()(Env&, JTx& jtx) const; }; -/** Sets the optional Expiration field on an NFTokenOffer. */ +/** + * Sets the optional Expiration field on an NFTokenOffer. + */ class Expiration { private: @@ -137,7 +159,9 @@ public: operator()(Env&, JTx& jtx) const; }; -/** Sets the optional Destination field on an NFTokenOffer. */ +/** + * Sets the optional Destination field on an NFTokenOffer. + */ class Destination { private: @@ -152,14 +176,18 @@ public: operator()(Env&, JTx& jtx) const; }; -/** Cancel NFTokenOffers. */ +/** + * Cancel NFTokenOffers. + */ json::Value cancelOffer(jtx::Account const& account, std::initializer_list const& nftokenOffers = {}); json::Value cancelOffer(jtx::Account const& account, std::vector const& nftokenOffers); -/** Sets the optional RootIndex field when canceling NFTokenOffers. */ +/** + * Sets the optional RootIndex field when canceling NFTokenOffers. + */ class RootIndex { private: @@ -174,22 +202,30 @@ public: operator()(Env&, JTx& jtx) const; }; -/** Accept an NFToken buy offer. */ +/** + * Accept an NFToken buy offer. + */ json::Value acceptBuyOffer(jtx::Account const& account, uint256 const& offerIndex); -/** Accept an NFToken sell offer. */ +/** + * Accept an NFToken sell offer. + */ json::Value acceptSellOffer(jtx::Account const& account, uint256 const& offerIndex); -/** Broker two NFToken offers. */ +/** + * Broker two NFToken offers. + */ json::Value brokerOffers( jtx::Account const& account, uint256 const& buyOfferIndex, uint256 const& sellOfferIndex); -/** Sets the optional NFTokenBrokerFee field in a brokerOffer transaction. */ +/** + * Sets the optional NFTokenBrokerFee field in a brokerOffer transaction. + */ class BrokerFee { private: @@ -204,15 +240,21 @@ public: operator()(Env&, JTx& jtx) const; }; -/** Set the authorized minter on an account root. */ +/** + * Set the authorized minter on an account root. + */ json::Value setMinter(jtx::Account const& account, jtx::Account const& minter); -/** Clear any authorized minter from an account root. */ +/** + * Clear any authorized minter from an account root. + */ json::Value clearMinter(jtx::Account const& account); -/** Modify an NFToken. */ +/** + * Modify an NFToken. + */ json::Value modify(jtx::Account const& account, uint256 const& nftokenID); diff --git a/src/test/jtx/trust.h b/src/test/jtx/trust.h index 034f80fcec..235780c825 100644 --- a/src/test/jtx/trust.h +++ b/src/test/jtx/trust.h @@ -10,11 +10,15 @@ namespace xrpl::test::jtx { -/** Modify a trust line. */ +/** + * Modify a trust line. + */ json::Value trust(Account const& account, STAmount const& amount, std::uint32_t flags = 0); -/** Change flags on a trust line. */ +/** + * Change flags on a trust line. + */ json::Value trust(Account const& account, STAmount const& amount, Account const& peer, std::uint32_t flags); diff --git a/src/test/jtx/txflags.h b/src/test/jtx/txflags.h index 7f5b31b2ac..c940681d82 100644 --- a/src/test/jtx/txflags.h +++ b/src/test/jtx/txflags.h @@ -7,7 +7,9 @@ namespace xrpl::test::jtx { -/** Set the flags on a JTx. */ +/** + * Set the flags on a JTx. + */ class Txflags { private: diff --git a/src/test/jtx/utility.h b/src/test/jtx/utility.h index f1cf3f7ae8..289afec8b3 100644 --- a/src/test/jtx/utility.h +++ b/src/test/jtx/utility.h @@ -13,7 +13,9 @@ namespace xrpl::test::jtx { -/** Thrown when parse fails. */ +/** + * Thrown when parse fails. + */ struct ParseError : std::logic_error { template @@ -22,35 +24,44 @@ struct ParseError : std::logic_error } }; -/** Convert JSON to STObject. - This throws on failure, the JSON must be correct. - @note Testing malformed JSON is beyond the scope of - this set of unit test routines. -*/ +/** + * Convert JSON to STObject. + * This throws on failure, the JSON must be correct. + * @note Testing malformed JSON is beyond the scope of + * this set of unit test routines. + */ STObject parse(json::Value const& jv); -/** Sign automatically into a specific Json field of the jv object. - @note This only works on accounts with multi-signing off. -*/ +/** + * Sign automatically into a specific Json field of the jv object. + * @note This only works on accounts with multi-signing off. + */ void sign(json::Value& jv, Account const& account, json::Value& sigObject); -/** Sign automatically. - @note This only works on accounts with multi-signing off. -*/ +/** + * Sign automatically. + * @note This only works on accounts with multi-signing off. + */ void sign(json::Value& jv, Account const& account); -/** Set the fee automatically. */ +/** + * Set the fee automatically. + */ void fillFee(json::Value& jv, ReadView const& view); -/** Set the sequence number automatically. */ +/** + * Set the sequence number automatically. + */ void fillSeq(json::Value& jv, ReadView const& view); -/** Given an xrpld unit test rpc command, return the corresponding JSON. */ +/** + * Given an xrpld unit test rpc command, return the corresponding JSON. + */ json::Value cmdToJSONRPC(std::vector const& args, beast::Journal j, unsigned int apiVersion); } // namespace xrpl::test::jtx diff --git a/src/test/jtx/vault.h b/src/test/jtx/vault.h index 4e6b90fe1f..e72eae89b7 100644 --- a/src/test/jtx/vault.h +++ b/src/test/jtx/vault.h @@ -27,7 +27,9 @@ struct Vault std::nullopt; // NOLINT(readability-redundant-member-init) }; - /** Return a VaultCreate transaction and the Vault's expected keylet. */ + /** + * Return a VaultCreate transaction and the Vault's expected keylet. + */ [[nodiscard]] std::tuple create(CreateArgs const& args) const; diff --git a/src/test/ledger/PaymentSandbox_test.cpp b/src/test/ledger/PaymentSandbox_test.cpp index f59b75091a..75f7410f94 100644 --- a/src/test/ledger/PaymentSandbox_test.cpp +++ b/src/test/ledger/PaymentSandbox_test.cpp @@ -1,17 +1,23 @@ #include #include #include +#include #include #include #include #include #include +#include +#include +#include +#include #include #include #include #include #include +#include #include #include #include @@ -19,7 +25,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -236,7 +244,7 @@ class PaymentSandbox_test : public beast::unit_test::Suite auto const startingAmount = accountHolds(pv, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j); - BEAST_EXPECT(issueIOU(pv, alice, toCredit, iss, j) == tesSUCCESS); + BEAST_EXPECT(issueIOU(pv, alice, toCredit, iss, {}, j) == tesSUCCESS); BEAST_EXPECT( accountHolds( pv, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j) == @@ -330,7 +338,7 @@ class PaymentSandbox_test : public beast::unit_test::Suite }; auto reserve = [](jtx::Env& env, std::uint32_t count) -> XRPAmount { - return env.current()->fees().accountReserve(count); + return env.current()->fees().accountReserve(count, 1); }; Env env(*this, features); @@ -389,6 +397,217 @@ class PaymentSandbox_test : public beast::unit_test::Suite BEAST_EXPECT(balance.getIssuer() == usd.account.id()); } + void + testOwnerCountHook(FeatureBitset features) + { + // Test that PaymentSandbox::adjustOwnerCountHook and ownerCountHook + // correctly track and return the maximum owner counts during a payment. + testcase("ownerCountHook"); + + using namespace jtx; + Env env(*this, features); + Account const alice("alice"); + Account const sponsor("sponsor"); + + env.fund(XRP(10000), alice, sponsor); + env.close(); + + ApplyViewImpl av(&*env.current(), TapNone); + PaymentSandbox sb(&av); + + // Test basic owner count hook without sponsor + { + auto const aliceSle = sb.peek(keylet::account(alice)); + BEAST_EXPECT(aliceSle); + + OwnerCounts const initial(aliceSle); + OwnerCounts updated = initial; + updated.owner = initial.owner + 2; + + // Simulate adjusting owner count + sb.adjustOwnerCountHook(alice, initial, updated); + + // ownerCountHook should return the max value + OwnerCounts const retrieved = sb.ownerCountHook(alice, initial); + BEAST_EXPECT(retrieved.owner == updated.owner); + BEAST_EXPECT(retrieved.sponsored == updated.sponsored); + BEAST_EXPECT(retrieved.sponsoring == updated.sponsoring); + } + + // Test owner count hook with sponsor-related counts + { + auto const sponsorSle = sb.peek(keylet::account(sponsor)); + BEAST_EXPECT(sponsorSle); + + OwnerCounts const sponsorInitial(sponsorSle); + OwnerCounts sponsorUpdated = sponsorInitial; + sponsorUpdated.owner = sponsorInitial.owner + 1; + sponsorUpdated.sponsoring = sponsorInitial.sponsoring + 1; + + sb.adjustOwnerCountHook(sponsor, sponsorInitial, sponsorUpdated); + + OwnerCounts const sponsorRetrieved = sb.ownerCountHook(sponsor, sponsorInitial); + BEAST_EXPECT(sponsorRetrieved.owner == sponsorUpdated.owner); + BEAST_EXPECT(sponsorRetrieved.sponsoring == sponsorUpdated.sponsoring); + } + + // Test with stacked PaymentSandboxes + { + PaymentSandbox sb2(&sb); + + auto const aliceSle = sb2.peek(keylet::account(alice)); + OwnerCounts const current(aliceSle); + OwnerCounts further = current; + further.owner = current.owner + 3; + + sb2.adjustOwnerCountHook(alice, current, further); + + // The nested sandbox should see the max from both levels + OwnerCounts const retrieved = sb2.ownerCountHook(alice, OwnerCounts()); + BEAST_EXPECT(retrieved.owner >= further.owner); + } + + // Test that max logic works correctly + { + auto const aliceSle = sb.peek(keylet::account(alice)); + OwnerCounts const current(aliceSle); + OwnerCounts lower = current; + lower.owner = (current.owner > 0) ? current.owner - 1 : 0; + + // Adjusting to a lower value + sb.adjustOwnerCountHook(alice, current, lower); + + // Should still return the higher value seen previously + OwnerCounts const retrieved = sb.ownerCountHook(alice, OwnerCounts()); + BEAST_EXPECT(retrieved.owner >= lower.owner); + } + } + + void + testOwnerCountWithTransaction(FeatureBitset features) + { + // Test that owner count hooks work correctly during actual transactions. + // This verifies that when transactions modify owner counts (by creating + // or deleting ledger objects), the hooks properly track these changes. + testcase( + std::string("ownerCountWithTransaction") + + (features[featureSponsor] ? " with sponsor" : " without sponsor")); + + using namespace jtx; + + auto reserve = [](jtx::Env& env, std::uint32_t count) -> XRPAmount { + return env.current()->fees().accountReserve(count, 1); + }; + + Env env(*this, features); + Account const gw("gw"); + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + + auto const usd = gw["USD"]; + + // Fund accounts. Alice starts with exactly enough for base reserve + 2 objects + env.fund(XRP(10000), gw, bob, sponsor); + env.fund(reserve(env, 3) + XRP(100), alice); // Base + 2 objects + extra for fees + env.close(); + + // Verify initial state - no owner count + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(ownerCount(env, bob) == 0); + + // Create a trust line - this increases owner count + env(trust(alice, usd(1000))); + env.close(); + + // alice now has 1 object (owner count = 1) + BEAST_EXPECT(ownerCount(env, alice) == 1); + + // Create an offer - this further increases owner count + env(trust(bob, usd(1000))); + env(pay(gw, alice, usd(100))); + env.close(); + + auto const aliceOfferSeq = env.seq(alice); // Capture the sequence before creating offer + env(offer(alice, usd(50), XRP(50))); + env.close(); + + // alice now has 2 objects (trust line + offer) + BEAST_EXPECT(ownerCount(env, alice) == 2); + + // If sponsor feature is enabled, test sponsorship transfer + if (features[featureSponsor]) + { + auto const trustId = keylet::trustLine(alice, gw, usd.currency); + BEAST_EXPECT(env.le(trustId)); + + // Transfer sponsorship - sponsor now sponsors alice's trust line + env(sponsor::transfer(alice, tfSponsorshipCreate, trustId.key), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + // alice still has 2 objects but 1 is sponsored + BEAST_EXPECT(ownerCount(env, alice) == 2); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + // sponsor's sponsoring count should increase + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + } + + // Verify alice's available balance respects the reserve + auto const aliceSle = env.le(keylet::account(alice)); + BEAST_EXPECT(aliceSle); + + auto const aliceBalance = aliceSle->getFieldAmount(sfBalance); + // With sponsor, 1 object is sponsored so only 1 counts for reserve + auto const aliceReserve = reserve(env, features[featureSponsor] ? 1 : 2); + + // alice should have limited available balance after accounting for reserve + auto const available = aliceBalance.xrp() - aliceReserve; + if (features[featureSponsor]) + { + // With sponsor, alice has more available (1 sponsored object = less reserve) + BEAST_EXPECT(available > XRP(150)); + } + else + { + BEAST_EXPECT(available < XRP(150)); // Most of the balance is in reserve + } + + // Try to send nearly all balance - should fail due to reserve in both cases + auto const tooMuch = aliceBalance.xrp() - XRP(1); + env(pay(alice, bob, tooMuch), Ter(tecUNFUNDED_PAYMENT)); + env.close(); + + // Verify owner count hasn't changed + BEAST_EXPECT(ownerCount(env, alice) == 2); + + // Cancel the offer - this decreases owner count + env(offerCancel(alice, aliceOfferSeq)); + env.close(); + + // alice now has 1 object (just the trust line) + BEAST_EXPECT(ownerCount(env, alice) == 1); + + if (features[featureSponsor]) + { + // Verify sponsored count stayed the same (trust line is still sponsored) + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + } + + // Now alice should have more available balance (less reserve needed) + auto const aliceSle2 = env.le(keylet::account(alice)); + auto const aliceBalance2 = aliceSle2->getFieldAmount(sfBalance); + // With sponsor, trust line is still sponsored so 0 objects for reserve + // Without sponsor, 1 object for reserve + auto const aliceReserve2 = reserve(env, features[featureSponsor] ? 0 : 1); + auto const available2 = aliceBalance2.xrp() - aliceReserve2; + + // available2 should be greater than available (less reserve needed) + BEAST_EXPECT(available2 > available); + } + public: void run() override @@ -399,11 +618,16 @@ public: testTinyBalance(features); testReserve(features); testBalanceHook(features); + testOwnerCountHook(features); }; using namespace jtx; auto const sa = testableAmendments(); testAll(sa - featurePermissionedDEX); testAll(sa); + + // Test owner count with transactions + testOwnerCountWithTransaction(sa - featureSponsor); + testOwnerCountWithTransaction(sa); } }; diff --git a/src/test/nodestore/TestBase.h b/src/test/nodestore/TestBase.h index a1245ec963..235e76501f 100644 --- a/src/test/nodestore/TestBase.h +++ b/src/test/nodestore/TestBase.h @@ -20,13 +20,14 @@ namespace xrpl::NodeStore { -/** Binary function that satisfies the strict-weak-ordering requirement. - - This compares the hashes of both objects and returns true if - the first hash is considered to go before the second. - - @see std::sort -*/ +/** + * Binary function that satisfies the strict-weak-ordering requirement. + * + * This compares the hashes of both objects and returns true if + * the first hash is considered to go before the second. + * + * @see std::sort + */ struct LessThan { bool @@ -37,7 +38,9 @@ struct LessThan } }; -/** Returns `true` if objects are identical. */ +/** + * Returns `true` if objects are identical. + */ inline bool isSame(std::shared_ptr const& lhs, std::shared_ptr const& rhs) { diff --git a/src/test/overlay/reduce_relay_test.cpp b/src/test/overlay/reduce_relay_test.cpp index 1c9bf1f9c1..c55729a45a 100644 --- a/src/test/overlay/reduce_relay_test.cpp +++ b/src/test/overlay/reduce_relay_test.cpp @@ -64,7 +64,8 @@ static constexpr std::uint32_t kMaxPeers = 10; static constexpr std::uint32_t kMaxValidators = 10; static constexpr std::uint32_t kMaxMessages = 200000; -/** Simulate two entities - peer directly connected to the server +/** + * Simulate two entities - peer directly connected to the server * (via squelch in PeerSim) and PeerImp (via Overlay) */ class PeerPartial : public Peer @@ -192,7 +193,9 @@ public: } }; -/** Manually advanced clock. */ +/** + * Manually advanced clock. + */ class ManualClock { public: @@ -238,7 +241,9 @@ private: inline static time_point kNow = time_point(seconds(0)); }; -/** Simulate server's OverlayImpl */ +/** + * Simulate server's OverlayImpl + */ class Overlay { public: @@ -260,7 +265,8 @@ public: class Validator; -/** Simulate link from a validator to a peer directly connected +/** + * Simulate link from a validator to a peer directly connected * to the server. */ class Link @@ -317,18 +323,19 @@ private: bool up_{true}; }; -/** Simulate Validator */ +/** + * Simulate Validator + */ class Validator { using Links = std::unordered_map; public: - Validator() : pkey_(std::get<0>(randomKeyPair(KeyType::Ed25519))) + Validator() : pkey_(std::get<0>(randomKeyPair(KeyType::Ed25519))), id_(sid++) { protocol::TMValidation v; v.set_validation("validation"); message_ = std::make_shared(v, protocol::mtVALIDATION, pkey_); - id_ = sid++; } Validator(Validator const&) = default; Validator(Validator&&) = default; @@ -401,14 +408,18 @@ public: } } - /** Send to specific peers */ + /** + * Send to specific peers + */ void send(std::vector peers, SquelchCB f) { forLinks(peers, [&](Link& link, MessageSPtr m) { link.send(m, f); }); } - /** Send to all peers */ + /** + * Send to all peers + */ void send(SquelchCB f) { @@ -457,7 +468,6 @@ public: using id_t = Peer::id_t; PeerSim(Overlay& overlay, beast::Journal journal) : overlay_(overlay), squelch_(journal) { - id_ = sid++; } ~PeerSim() override = default; @@ -480,7 +490,9 @@ public: sid = 0; } - /** Local Peer (PeerImp) */ + /** + * Local Peer (PeerImp) + */ void onMessage(MessageSPtr const& m, SquelchCB f) override { @@ -493,7 +505,9 @@ public: {}, *validator, id(), f); // NOLINT(bugprone-unchecked-optional-access) } - /** Remote Peer (Directly connected Peer) */ + /** + * Remote Peer (Directly connected Peer) + */ void onMessage(protocol::TMSquelch const& squelch) override { @@ -512,7 +526,7 @@ public: private: inline static id_t sid = 0; std::string fingerprint_; - id_t id_; + id_t id_{sid++}; Overlay& overlay_; reduce_relay::Squelch squelch_; }; @@ -838,7 +852,9 @@ public: } } - /** Is peer in Selected state in any of the slots */ + /** + * Is peer in Selected state in any of the slots + */ bool isSelected(Peer::id_t id) { @@ -850,7 +866,8 @@ public: return false; } - /** Check if there are peers to unsquelch - peer is in Selected + /** + * Check if there are peers to unsquelch - peer is in Selected * state in any of the slots and there are peers in Squelched state * in those slots. */ @@ -894,7 +911,9 @@ protected: std::cout << std::endl; } - /** Send squelch (if duration is set) or unsquelch (if duration not set) */ + /** + * Send squelch (if duration is set) or unsquelch (if duration not set) + */ static Peer::id_t sendSquelch( PublicKey const& validator, @@ -932,7 +951,8 @@ protected: bool handled = false; }; - /** Randomly brings the link between a validator and a peer down. + /** + * Randomly brings the link between a validator and a peer down. * Randomly disconnects a peer. Those events are generated one at a time. */ void @@ -1110,7 +1130,8 @@ protected: f(log); } - /** Initial counting round: three peers receive message "faster" then + /** + * Initial counting round: three peers receive message "faster" then * others. Once the message count for the three peers reaches threshold * the rest of the peers are squelched and the slot for the given validator * is in Selected state. @@ -1121,7 +1142,8 @@ protected: doTest("Initial Round", log, [this](bool log) { BEAST_EXPECT(propagateAndSquelch(log)); }); } - /** Receiving message from squelched peer too soon should not change the + /** + * Receiving message from squelched peer too soon should not change the * slot's state to Counting. */ void @@ -1132,7 +1154,8 @@ protected: }); } - /** Receiving message from squelched peer should change the + /** + * Receiving message from squelched peer should change the * slot's state to Counting. */ void @@ -1144,7 +1167,9 @@ protected: }); } - /** Propagate enough messages to generate one squelch event */ + /** + * Propagate enough messages to generate one squelch event + */ bool propagateAndSquelch(bool log, bool purge = true, bool resetClock = true) { @@ -1178,7 +1203,9 @@ protected: return n == 1 && res; } - /** Send fewer message so that squelch event is not generated */ + /** + * Send fewer message so that squelch event is not generated + */ bool propagateNoSquelch( bool log, @@ -1205,7 +1232,8 @@ protected: return !squelched && res; } - /** Receiving a message from new peer should change the + /** + * Receiving a message from new peer should change the * slot's state to Counting. */ void @@ -1218,8 +1246,10 @@ protected: }); } - /** Selected peer disconnects. Should change the state to counting and - * unsquelch squelched peers. */ + /** + * Selected peer disconnects. Should change the state to counting and + * unsquelch squelched peers. + */ void testSelectedPeerDisconnects(bool log) { @@ -1237,8 +1267,10 @@ protected: }); } - /** Selected peer stops relaying. Should change the state to counting and - * unsquelch squelched peers. */ + /** + * Selected peer stops relaying. Should change the state to counting and + * unsquelch squelched peers. + */ void testSelectedPeerStopsRelaying(bool log) { @@ -1257,7 +1289,8 @@ protected: }); } - /** Squelched peer disconnects. Should not change the state to counting. + /** + * Squelched peer disconnects. Should not change the state to counting. */ void testSquelchedPeerDisconnects(bool log) diff --git a/src/test/protocol/STTx_test.cpp b/src/test/protocol/STTx_test.cpp index 21518d1406..45bd2729c4 100644 --- a/src/test/protocol/STTx_test.cpp +++ b/src/test/protocol/STTx_test.cpp @@ -1366,6 +1366,7 @@ public: { fail("Unable to build object from json"); } + // NOLINTNEXTLINE(cppcoreguidelines-slicing) else if (STObject(j) != parsed.object) { log << "ORIG: " << j.getJson(JsonOptions::Values::None) << '\n' diff --git a/src/test/resource/Logic_test.cpp b/src/test/resource/Logic_test.cpp deleted file mode 100644 index 40a81503f1..0000000000 --- a/src/test/resource/Logic_test.cpp +++ /dev/null @@ -1,296 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include - -namespace xrpl::Resource { - -class ResourceManager_test : public beast::unit_test::Suite -{ -public: - class TestLogic : private boost::base_from_member, public Logic - - { - private: - using clock_type = boost::base_from_member; - - public: - explicit TestLogic(beast::Journal journal) - : Logic(beast::insight::NullCollector::make(), member, journal) - { - } - - void - advance() - { - ++member; - } - - TestStopwatch& - clock() - { - return member; - } - }; - - //-------------------------------------------------------------------------- - - static void - createGossip(Gossip& gossip) - { - std::uint8_t const v(10 + randInt(9)); - std::uint8_t const n(10 + randInt(9)); - gossip.items.reserve(n); - for (std::uint8_t i = 0; i < n; ++i) - { - Gossip::Item item; - item.balance = 100 + randInt(499); - beast::IP::AddressV4::bytes_type const d = { - {192, 0, 2, static_cast(v + i)}}; - item.address = beast::IP::Endpoint{beast::IP::AddressV4{d}}; - gossip.items.push_back(item); - } - } - - //-------------------------------------------------------------------------- - - void - testDrop(beast::Journal j, bool limited) - { - if (limited) - { - testcase("Limited warn/drop"); - } - else - { - testcase("Unlimited warn/drop"); - } - - TestLogic logic(j); - - Charge const fee(kDropThreshold + 1); - beast::IP::Endpoint const addr(beast::IP::Endpoint::fromString("192.0.2.2")); - - std::function const ep = - [&logic, limited](beast::IP::Endpoint const& address) { - return limited ? logic.newInboundEndpoint(address) - : logic.newUnlimitedEndpoint(address); - }; - - { - Consumer c(ep(addr)); - - // Create load until we get a warning - int n = 10000; - - while (--n >= 0) - { - if (n == 0) - { - if (limited) - { - fail("Loop count exceeded without warning"); - } - else - { - pass(); - } - return; - } - - if (c.charge(fee) == Disposition::Warn) - { - if (limited) - { - pass(); - } - else - { - fail("Should loop forever with no warning"); - } - break; - } - ++logic.clock(); - } - - // Create load until we get dropped - while (--n >= 0) - { - if (n == 0) - { - if (limited) - { - fail("Loop count exceeded without dropping"); - } - else - { - pass(); - } - return; - } - - if (c.charge(fee) == Disposition::Drop) - { - // Disconnect abusive Consumer - BEAST_EXPECT(c.disconnect(j) == limited); - break; - } - ++logic.clock(); - } - } - - // Make sure the consumer is on the blacklist for a while. - { - Consumer const c(logic.newInboundEndpoint(addr)); - logic.periodicActivity(); - if (c.disposition() != Disposition::Drop) - { - if (limited) - { - fail("Dropped consumer not put on blacklist"); - } - else - { - pass(); - } - return; - } - } - - // Makes sure the Consumer is eventually removed from blacklist - bool readmitted = false; - { - using namespace std::chrono_literals; - // Give Consumer time to become readmitted. Should never - // exceed expiration time. - auto n = kSecondsUntilExpiration + 1s; - while (--n > 0s) - { - ++logic.clock(); - logic.periodicActivity(); - Consumer const c(logic.newInboundEndpoint(addr)); - if (c.disposition() != Disposition::Drop) - { - readmitted = true; - break; - } - } - } - if (!readmitted) - { - fail("Dropped Consumer left on blacklist too long"); - return; - } - pass(); - } - - void - testImports(beast::Journal j) - { - testcase("Imports"); - - TestLogic logic(j); - - Gossip g[5]; - - for (auto& gossip : g) - createGossip(gossip); - - for (int i = 0; i < 5; ++i) - logic.importConsumers(std::to_string(i), g[i]); - - pass(); - } - - void - testImport(beast::Journal j) - { - testcase("Import"); - - TestLogic logic(j); - - Gossip g; - Gossip::Item item; - item.balance = 100; - beast::IP::AddressV4::bytes_type const d = {{192, 0, 2, 1}}; - item.address = beast::IP::Endpoint{beast::IP::AddressV4{d}}; - g.items.push_back(item); - - logic.importConsumers("g", g); - - pass(); - } - - void - testCharges(beast::Journal j) - { - testcase("Charge"); - - TestLogic logic(j); - - { - beast::IP::Endpoint const address(beast::IP::Endpoint::fromString("192.0.2.1")); - Consumer c(logic.newInboundEndpoint(address)); - Charge const fee(1000); - JLOG(j.info()) << "Charging " << c.toString() << " " << fee << " per second"; - c.charge(fee); - for (int i = 0; i < 128; ++i) - { - JLOG(j.info()) << "Time= " << logic.clock().now().time_since_epoch().count() - << ", Balance = " << c.balance(); - logic.advance(); - } - } - - { - beast::IP::Endpoint const address(beast::IP::Endpoint::fromString("192.0.2.2")); - Consumer c(logic.newInboundEndpoint(address)); - Charge const fee(1000); - JLOG(j.info()) << "Charging " << c.toString() << " " << fee << " per second"; - for (int i = 0; i < 128; ++i) - { - c.charge(fee); - JLOG(j.info()) << "Time= " << logic.clock().now().time_since_epoch().count() - << ", Balance = " << c.balance(); - logic.advance(); - } - } - - pass(); - } - - void - run() override - { - using beast::Severity; - test::SuiteJournal journal("ResourceManager_test", *this); - - testDrop(journal, true); - testDrop(journal, false); - testCharges(journal); - testImports(journal); - testImport(journal); - } -}; - -BEAST_DEFINE_TESTSUITE(ResourceManager, resource, xrpl); - -} // namespace xrpl::Resource diff --git a/src/test/rpc/AccountObjects_test.cpp b/src/test/rpc/AccountObjects_test.cpp index 31b20b37d4..6ba6e7de9f 100644 --- a/src/test/rpc/AccountObjects_test.cpp +++ b/src/test/rpc/AccountObjects_test.cpp @@ -9,8 +9,11 @@ #include // IWYU pragma: keep #include #include +#include +#include #include #include +#include #include #include @@ -22,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -200,6 +204,15 @@ public: resp[jss::result][jss::error_message] == "Invalid field 'limit', not unsigned integer."); } + // test error on sponsored param not a boolean + { + json::Value params; + params[jss::account] = bob.human(); + params[jss::sponsored] = "true"; + auto resp = env.rpc("json", "account_objects", to_string(params)); + BEAST_EXPECT( + resp[jss::result][jss::error_message] == "Invalid field 'sponsored', not boolean."); + } // test errors on marker { Account const gw{"G"}; @@ -605,6 +618,7 @@ public: BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::amm), 0)); BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::did), 0)); BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::permissioned_domain), 0)); + BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::sponsorship), 0)); // we expect invalid field type reported for the following types BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::amendments))); @@ -926,6 +940,30 @@ public: BEAST_EXPECT(ticket[sfTicketSequence.jsonName].asUInt() == seq + 1); } + { + // Create a sponsorship + env(sponsor::set(alice, tfSponsorshipSetRequireSignForFee, 200, XRP(100), drops(10)), + sponsor::SponseeAcc(gw)); + env.close(); + + // Find the sponsorship. + for (auto const& acct : {alice, gw}) + { + json::Value const resp = acctObjs(acct, jss::sponsorship); + BEAST_EXPECT(acctObjsIsSize(resp, 1)); + + auto const& sponsorship = resp[jss::result][jss::account_objects][0u]; + + BEAST_EXPECT(sponsorship[sfOwner.jsonName] == alice.human()); + BEAST_EXPECT(sponsorship[sfSponsee.jsonName] == gw.human()); + BEAST_EXPECT( + sponsorship[sfFlags.jsonName].asUInt() == tfSponsorshipSetRequireSignForFee); + BEAST_EXPECT(sponsorship[sfRemainingOwnerCount.jsonName].asUInt() == 200); + BEAST_EXPECT(sponsorship[sfFeeAmount.jsonName].asUInt() == 100000000); + BEAST_EXPECT(sponsorship[sfMaxFee.jsonName].asUInt() == 10); + } + } + { // See how "deletion_blockers_only" handles gw's directory. json::Value params; @@ -940,7 +978,8 @@ public: jss::NFTokenPage.cStr(), jss::RippleState.cStr(), jss::PayChannel.cStr(), - jss::PermissionedDomain.cStr()}; + jss::PermissionedDomain.cStr(), + jss::Sponsorship.cStr()}; std::ranges::sort(v); return v; }(); @@ -1350,6 +1389,233 @@ public: } } + void + testSponsoredFilter() + { + testcase("SponsoredFilter"); + using namespace jtx; + + Env env(*this, testableAmendments()); + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor1("sponsor1"); + Account const gw("gw"); + auto const usd = gw["USD"]; + + env.fund(XRP(10000), alice, bob, sponsor1, gw); + env.close(); + + // Helper to call account_objects with sponsored filter + auto acctObjsSponsored = [](Env& testEnv, + AccountID const& acct, + bool sponsored, + std::optional const& type = std::nullopt) { + json::Value params; + params[jss::account] = to_string(acct); + params[jss::sponsored] = sponsored; + if (type) + params[jss::type] = *type; + params[jss::ledger_index] = "validated"; + return testEnv.rpc("json", "account_objects", to_string(params)); + }; + + // Create a trust line for bob (not sponsored) + env(trust(bob, usd(1000))); + env.close(); + + // sponsored=true should not find any objects for bob (doesn't have any sponsored objects) + { + auto const resp = acctObjsSponsored(env, bob.id(), true); + auto const& objs = resp[jss::result][jss::account_objects]; + BEAST_EXPECT(objs.size() == 0); + } + + // Now sponsor bob's trust line + auto const trustId = keylet::trustLine(bob, gw, usd.currency); + if (!BEAST_EXPECT(env.le(trustId))) + return; + + env(sponsor::transfer(bob, tfSponsorshipCreate, trustId.key), + sponsor::As(sponsor1, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor1)); + env.close(); + + // Verify trust line has sponsor field + { + auto const sle = env.le(trustId); + if (!BEAST_EXPECT(sle)) + return; + BEAST_EXPECT(sle->isFieldPresent(sfHighSponsor) || sle->isFieldPresent(sfLowSponsor)); + } + + // sponsored=true on bob should include the sponsored trust line + { + auto const resp = acctObjsSponsored(env, bob.id(), true); + auto const& objs = resp[jss::result][jss::account_objects]; + if (!BEAST_EXPECT(objs.size() == 1)) + return; + + auto const& obj = objs[0u]; + BEAST_EXPECT(obj[sfLedgerEntryType.jsonName] == jss::RippleState); + BEAST_EXPECT( + obj.isMember(sfHighSponsor.jsonName) || obj.isMember(sfLowSponsor.jsonName)); + } + + // sponsored=false on bob should NOT include the sponsored trust line + { + auto const resp = acctObjsSponsored(env, bob.id(), false); + BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 0); + } + + // A trust line sponsored on either side is classified as sponsored + // for both parties. + { + Env env(*this, testableAmendments()); + Account const issuer("issuer"); + Account const user("user"); + Account const sponsor("sponsor"); + auto const usd = issuer["USD"]; + + env.fund(XRP(10000), issuer, user, sponsor); + env.close(); + + env(trust(issuer, user["USD"](100))); + env.close(); + + env(trust(user, usd(100))); + env.close(); + + auto const trustId = keylet::trustLine(user, issuer, usd.currency); + if (!BEAST_EXPECT(env.le(trustId))) + return; + + env(sponsor::transfer(user, tfSponsorshipCreate, trustId.key), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + auto const line = env.le(trustId); + if (!BEAST_EXPECT(line)) + return; + + auto const userIsHigh = line->getFieldAmount(sfHighLimit).getIssuer() == user.id(); + auto const& userSponsorField = userIsHigh ? sfHighSponsor : sfLowSponsor; + auto const& issuerSponsorField = userIsHigh ? sfLowSponsor : sfHighSponsor; + + BEAST_EXPECT(line->isFieldPresent(userSponsorField)); + BEAST_EXPECT(!line->isFieldPresent(issuerSponsorField)); + + { + auto const resp = acctObjsSponsored(env, user.id(), true, jss::state); + auto const& objs = resp[jss::result][jss::account_objects]; + if (BEAST_EXPECT(objs.size() == 1)) + BEAST_EXPECT(objs[0u][sfLedgerEntryType.jsonName] == jss::RippleState); + } + { + auto const resp = acctObjsSponsored(env, user.id(), false, jss::state); + auto const& objs = resp[jss::result][jss::account_objects]; + BEAST_EXPECT(objs.size() == 0); + } + { + auto const resp = acctObjsSponsored(env, issuer.id(), true, jss::state); + auto const& objs = resp[jss::result][jss::account_objects]; + if (BEAST_EXPECT(objs.size() == 1)) + BEAST_EXPECT(objs[0u][sfLedgerEntryType.jsonName] == jss::RippleState); + } + { + auto const resp = acctObjsSponsored(env, issuer.id(), false, jss::state); + auto const& objs = resp[jss::result][jss::account_objects]; + BEAST_EXPECT(objs.size() == 0); + } + } + + // A sponsored Check is classified as sponsored in both the writer's + // and the destination's results. + { + Env env(*this, testableAmendments()); + Account const owner("owner"); + Account const dest("dest"); + Account const sponsor("sponsor"); + + env.fund(XRP(10000), owner, dest, sponsor); + env.close(); + + auto const checkSeq = env.seq(owner); + env(check::create(owner, dest, XRP(1))); + env.close(); + + auto const checkId = keylet::check(owner, checkSeq); + if (!BEAST_EXPECT(env.le(checkId))) + return; + + env(sponsor::transfer(owner, tfSponsorshipCreate, checkId.key), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + { + auto const sle = env.le(checkId); + if (!BEAST_EXPECT(sle)) + return; + BEAST_EXPECT(sle->isFieldPresent(sfSponsor)); + } + + for (auto const& acct : {owner.id(), dest.id()}) + { + { + auto const resp = acctObjsSponsored(env, acct, true, jss::check); + auto const& objs = resp[jss::result][jss::account_objects]; + if (BEAST_EXPECT(objs.size() == 1)) + BEAST_EXPECT(objs[0u][sfLedgerEntryType.jsonName] == jss::Check); + } + { + auto const resp = acctObjsSponsored(env, acct, false, jss::check); + BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 0); + } + } + } + + // A Sponsorship object is visible to both sides. + { + Env env(*this, testableAmendments()); + Account const owner("owner"); + Account const sponsee("sponsee"); + + env.fund(XRP(10000), owner, sponsee); + env.close(); + + env(sponsor::set(owner, 0, 100, XRP(100)), sponsor::SponseeAcc(sponsee)); + env.close(); + + auto const sponsorshipKeylet = keylet::sponsorship(owner, sponsee); + if (!BEAST_EXPECT(env.le(sponsorshipKeylet))) + return; + + { + auto const resp = acctObjsSponsored(env, owner.id(), false, jss::sponsorship); + auto const& objs = resp[jss::result][jss::account_objects]; + if (BEAST_EXPECT(objs.size() == 1)) + BEAST_EXPECT(objs[0u][sfLedgerEntryType.jsonName] == jss::Sponsorship); + } + { + auto const resp = acctObjsSponsored(env, sponsee.id(), false, jss::sponsorship); + auto const& objs = resp[jss::result][jss::account_objects]; + if (BEAST_EXPECT(objs.size() == 1)) + BEAST_EXPECT(objs[0u][sfLedgerEntryType.jsonName] == jss::Sponsorship); + } + { + auto const resp = acctObjsSponsored(env, owner.id(), true, jss::sponsorship); + auto const& objs = resp[jss::result][jss::account_objects]; + BEAST_EXPECT(objs.size() == 0); + } + { + auto const resp = acctObjsSponsored(env, sponsee.id(), true, jss::sponsorship); + auto const& objs = resp[jss::result][jss::account_objects]; + BEAST_EXPECT(objs.size() == 0); + } + } + } + void run() override { @@ -1360,6 +1626,7 @@ public: testNFTsMarker(); testAccountNFTs(); testAccountObjectMarker(); + testSponsoredFilter(); } }; diff --git a/src/test/rpc/AccountTx_test.cpp b/src/test/rpc/AccountTx_test.cpp index ace8743e1e..a7f37d39b9 100644 --- a/src/test/rpc/AccountTx_test.cpp +++ b/src/test/rpc/AccountTx_test.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -773,7 +774,7 @@ class AccountTx_test : public beast::unit_test::Suite // All it takes is a large enough XRP payment to resurrect // becky's account. Try too small a payment. - env(pay(alice, becky, drops(env.current()->fees().accountReserve(0)) - drops(1)), + env(pay(alice, becky, drops(env.current()->fees().accountReserve(0, 1)) - drops(1)), Ter(tecNO_DST_INSUF_XRP)); env.close(); @@ -888,6 +889,82 @@ class AccountTx_test : public beast::unit_test::Suite checkAliceAcctTx(9, jss::Payment); } + void + testSponsorship() + { + // test all sponsored transactions are in sponsor and sponsee's account + // tx list + testcase("Sponsorship"); + + using namespace test::jtx; + Env env(*this); + Account const alice("alice"); + Account const sponsor("sponsor"); + Account const sponsor2("sponsor2"); + env.fund(XRP(10000), alice, sponsor, sponsor2); + env.close(); + + // check the latest sponsorship-related txn is in account tx list + auto const checkTx = [&](Account const& account, json::StaticString txType) { + json::Value params; + params[jss::account] = account.human(); + params[jss::limit] = 100; + auto const jv = env.rpc("json", "account_tx", to_string(params))[jss::result]; + + auto const& tx0(jv[jss::transactions][0u][jss::tx]); + BEAST_EXPECT(tx0[jss::TransactionType] == txType); + + std::string const txHash{ + env.tx()->getJson(JsonOptions::Values::None)[jss::hash].asString()}; + BEAST_EXPECT(tx0[jss::hash] == txHash); + }; + + // fee sponsorship + env(noop(alice), sponsor::As(sponsor, spfSponsorFee), Sig(sfSponsorSignature, sponsor)); + env.close(); + checkTx(alice, jss::AccountSet); + checkTx(sponsor, jss::AccountSet); + + // set sponsor + env(sponsor::set(sponsor, 0, 100, XRP(100)), sponsor::SponseeAcc(alice), Ter(tesSUCCESS)); + env.close(); + checkTx(alice, jss::SponsorshipSet); + checkTx(sponsor, jss::SponsorshipSet); + + // create an object with sponsor + auto const checkId = keylet::check(alice, env.seq(alice)).key; + env(check::create(alice, sponsor, XRP(1)), sponsor::As(sponsor, spfSponsorReserve)); + env.close(); + checkTx(alice, jss::CheckCreate); + checkTx(sponsor, jss::CheckCreate); + + // transfer object sponsorship + env(sponsor::transfer(alice, tfSponsorshipReassign, checkId), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + checkTx(alice, jss::SponsorshipTransfer); + checkTx(sponsor, jss::SponsorshipTransfer); + checkTx(sponsor2, jss::SponsorshipTransfer); + + // delete the sponsored object + env(check::cancel(alice, checkId), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor)); + env.close(); + checkTx(alice, jss::CheckCancel); + checkTx(sponsor, jss::CheckCancel); + checkTx(sponsor2, jss::CheckCancel); + + // account sponsorship + env(sponsor::transfer(alice, tfSponsorshipCreate), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + checkTx(alice, jss::SponsorshipTransfer); + checkTx(sponsor, jss::SponsorshipTransfer); + } + public: void run() override @@ -896,6 +973,7 @@ public: testContents(); testAccountDelete(); testMPT(); + testSponsorship(); } }; BEAST_DEFINE_TESTSUITE(AccountTx, rpc, xrpl); diff --git a/src/test/rpc/LedgerEntry_test.cpp b/src/test/rpc/LedgerEntry_test.cpp index c7ef5cef2d..7adb5a4518 100644 --- a/src/test/rpc/LedgerEntry_test.cpp +++ b/src/test/rpc/LedgerEntry_test.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -93,6 +94,8 @@ std::vector> gMappings{ {jss::oracle_document_id, FieldType::UInt32Field}, {jss::owner, FieldType::AccountField}, {jss::seq, FieldType::UInt32Field}, + {jss::sponsor, FieldType::AccountField}, + {jss::sponsee, FieldType::AccountField}, {jss::subject, FieldType::AccountField}, {jss::ticket_seq, FieldType::UInt32Field}, }; @@ -107,7 +110,7 @@ getFieldType(json::StaticString fieldName) return it->second; } - Throw("`mappings` is missing field " + std::string(fieldName.cStr())); + Throw("`gMappings` is missing field " + std::string(fieldName.cStr())); } std::string @@ -1884,6 +1887,59 @@ class LedgerEntry_test : public beast::unit_test::Suite runLedgerEntryTest(env, jss::signer_list); } + void + testSponsorship() + { + testcase("Sponsorship"); + + using namespace test::jtx; + + Env env{*this}; + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(10000), alice, bob); + env.close(); + env(sponsor::set(alice, 0, 100), sponsor::SponseeAcc(bob)); + env.close(); + std::string const ledgerHash{to_string(env.closed()->header().hash)}; + auto const sponsorshipIndex = to_string(keylet::sponsorship(alice.id(), bob.id()).key); + + { + // Request by sponsor and sponsee. + json::Value jvParams; + jvParams[jss::sponsorship][jss::sponsor] = alice.human(); + jvParams[jss::sponsorship][jss::sponsee] = bob.human(); + jvParams[jss::ledger_hash] = ledgerHash; + auto const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Sponsorship); + BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == alice.human()); + BEAST_EXPECT(jrr[jss::node][sfSponsee.jsonName] == bob.human()); + BEAST_EXPECT(sponsorshipIndex == jrr[jss::node][jss::index].asString()); + } + { + // Request by index. + json::Value jvParams; + jvParams[jss::sponsorship] = sponsorshipIndex; + jvParams[jss::ledger_hash] = ledgerHash; + json::Value const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Sponsorship); + BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == alice.human()); + BEAST_EXPECT(jrr[jss::node][sfSponsee.jsonName] == bob.human()); + BEAST_EXPECT(sponsorshipIndex == jrr[jss::node][jss::index].asString()); + } + { + // Check all malformed cases. + runLedgerEntryTest( + env, + jss::sponsorship, + { + {.fieldName = jss::sponsor, .malformedErrorMsg = "malformedSponsor"}, + {.fieldName = jss::sponsee, .malformedErrorMsg = "malformedSponsee"}, + }); + } + } + void testTicket() { @@ -2232,7 +2288,9 @@ class LedgerEntry_test : public beast::unit_test::Suite } } - /// Test the ledger entry types that don't take parameters + /** + * Test the ledger entry types that don't take parameters + */ void testFixed() { @@ -2248,7 +2306,8 @@ class LedgerEntry_test : public beast::unit_test::Suite env.close(); - /** Verifies that the RPC result has the expected data + /** + * Verifies that the RPC result has the expected data * * @param good: Indicates that the request should have succeeded * and returned a ledger object of `expectedType` type. @@ -2283,7 +2342,8 @@ class LedgerEntry_test : public beast::unit_test::Suite } }; - /** Runs a series of tests for a given fixed-position ledger + /** + * Runs a series of tests for a given fixed-position ledger * entry. * * @param field: The Json request field to use. @@ -2441,7 +2501,8 @@ class LedgerEntry_test : public beast::unit_test::Suite env.close(); - /** Verifies that the RPC result has the expected data + /** + * Verifies that the RPC result has the expected data * * @param good: Indicates that the request should have succeeded * and returned a ledger object of `expectedType` type. @@ -2481,7 +2542,8 @@ class LedgerEntry_test : public beast::unit_test::Suite } }; - /** Runs a series of tests for a given ledger index. + /** + * Runs a series of tests for a given ledger index. * * @param ledger: The ledger index value of the "hashes" request * parameter. May not necessarily be a number. @@ -2676,6 +2738,7 @@ public: testPayChan(); testRippleState(); testSignerList(); + testSponsorship(); testTicket(); testDID(); testInvalidOracleLedgerEntry(); diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp index 4f3b3ebd9a..73a56c8ad1 100644 --- a/src/test/rpc/LedgerRPC_test.cpp +++ b/src/test/rpc/LedgerRPC_test.cpp @@ -356,10 +356,12 @@ class LedgerRPC_test : public beast::unit_test::Suite BEAST_EXPECT((*mptOfferTx)[jss::owner_funds] == "100"); } - /// @brief ledger RPC requests as a way to drive - /// input options to lookupLedger. The point of this test is - /// coverage for lookupLedger, not so much the ledger - /// RPC request. + /** + * @brief ledger RPC requests as a way to drive + * input options to lookupLedger. The point of this test is + * coverage for lookupLedger, not so much the ledger + * RPC request. + */ void testLookupLedger() { diff --git a/src/test/rpc/Simulate_test.cpp b/src/test/rpc/Simulate_test.cpp index 0d00360a58..16df733a66 100644 --- a/src/test/rpc/Simulate_test.cpp +++ b/src/test/rpc/Simulate_test.cpp @@ -364,6 +364,37 @@ class Simulate_test : public beast::unit_test::Suite auto const resp = env.rpc("json", "simulate", to_string(params)); BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'tx.Signers[0]'."); } + { + // Non-object SponsorSignature field + json::Value params; + json::Value txJson = json::ValueType::Object; + txJson[jss::TransactionType] = jss::AccountSet; + txJson[jss::Account] = env.master.human(); + txJson[sfSponsorSignature] = ""; + params[jss::tx_json] = txJson; + + auto const resp = env.rpc("json", "simulate", to_string(params)); + BEAST_EXPECT( + resp[jss::result][jss::error_message] == + "Invalid field 'SponsorSignature', not object."); + } + { + // Invalid SponsorSignature.Signers field + json::Value params; + json::Value txJson = json::ValueType::Object; + txJson[jss::TransactionType] = jss::AccountSet; + txJson[jss::Account] = env.master.human(); + json::Value sponsorSignature = json::ValueType::Object; + sponsorSignature[sfSigners] = "1"; + txJson[sfSponsorSignature] = sponsorSignature; + params[jss::tx_json] = txJson; + + auto const resp = env.rpc("json", "simulate", to_string(params)); + BEAST_EXPECTS( + resp[jss::result][jss::error_message] == + "Invalid field 'tx.SponsorSignature.Signers'.", + resp.toStyledString()); + } { // Invalid transaction json::Value params; @@ -561,6 +592,75 @@ class Simulate_test : public beast::unit_test::Suite // test without autofill testTx(env, tx, validateOutput); } + + { + // autofill sponsor signature + + auto validateOutput = [&](json::Value const& resp, json::Value const& tx) { + auto result = resp[jss::result]; + checkBasicReturnValidity( + result, tx, env.seq(env.master), env.current()->fees().base); + + BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS"); + BEAST_EXPECT(result[jss::engine_result_code] == 0); + BEAST_EXPECT( + result[jss::engine_result_message] == + "The simulated transaction would have been applied."); + + if (BEAST_EXPECT(result.isMember(jss::meta) || result.isMember(jss::meta_blob))) + { + json::Value const metadata = getJsonMetadata(result); + + if (BEAST_EXPECT(metadata.isMember(sfAffectedNodes.jsonName))) + { + BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].size() == 2); + + auto node = metadata[sfAffectedNodes.jsonName][0u]; + if (BEAST_EXPECT(node.isMember(sfModifiedNode.jsonName))) + { + auto modifiedNode = node[sfModifiedNode]; + BEAST_EXPECT(modifiedNode[sfLedgerEntryType] == "AccountRoot"); + auto previousFields = modifiedNode[sfPreviousFields]; + BEAST_EXPECT(!previousFields.isMember(sfBalance.jsonName)); + } + + auto node2 = metadata[sfAffectedNodes.jsonName][1u]; + if (BEAST_EXPECT(node2.isMember(sfModifiedNode.jsonName))) + { + auto modifiedNode = node2[sfModifiedNode]; + BEAST_EXPECT(modifiedNode[sfLedgerEntryType] == "AccountRoot"); + + auto previousFields = modifiedNode[sfPreviousFields]; + BEAST_EXPECT(previousFields.isMember(sfBalance.jsonName)); + } + } + BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0); + BEAST_EXPECT(metadata[sfTransactionResult.jsonName] == "tesSUCCESS"); + } + }; + + Account const sponsor("sponsor"); + env.fund(XRP(10000), sponsor); + env.close(); + + json::Value tx; + + tx[jss::Account] = env.master.human(); + tx[jss::TransactionType] = jss::AccountSet; + tx[sfDomain.jsonName] = kNewDomain; + tx[sfSponsor.jsonName] = sponsor.human(); + tx[sfSponsorFlags.jsonName] = spfSponsorFee; + tx[sfSponsorSignature.jsonName] = json::ValueType::Object; + + // test with autofill + testTx(env, tx, validateOutput); + + tx[sfSponsorSignature.jsonName][sfTxnSignature.jsonName] = ""; + tx[sfSponsorSignature.jsonName][sfSigningPubKey.jsonName] = ""; + + // test without autofill + testTx(env, tx, validateOutput); + } } void @@ -763,6 +863,58 @@ class Simulate_test : public beast::unit_test::Suite } } + void + testSuccessfulSponsoredTransactionMultisigned() + { + testcase("Successful sponsored multi-signed transaction"); + + using namespace jtx; + Env env(*this); + Account const sponsor("sponsor"); + Account const signer("signer"); + env.fund(XRP(10000), sponsor, signer); + env.close(); + + env(signers(sponsor, 1, {{signer, 1}})); + env.close(); + + auto validateOutput = [&](json::Value const& resp, json::Value const& tx) { + auto const result = resp[jss::result]; + // Verifies Fee autofill counts nested sponsor-signature signers. + auto const expectedFee = env.current()->fees().base * 2; + checkBasicReturnValidity(result, tx, env.seq(env.master), expectedFee); + + BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS"); + BEAST_EXPECT(result[jss::engine_result_code] == 0); + BEAST_EXPECT( + result[jss::engine_result_message] == + "The simulated transaction would have been applied."); + + if (BEAST_EXPECT(result.isMember(jss::meta) || result.isMember(jss::meta_blob))) + { + json::Value const metadata = getJsonMetadata(result); + BEAST_EXPECT(metadata[sfTransactionResult.jsonName] == "tesSUCCESS"); + } + }; + + json::Value tx; + tx[jss::Account] = env.master.human(); + tx[jss::TransactionType] = jss::AccountSet; + tx[sfDomain] = "123ABC"; + tx[sfSponsor.jsonName] = sponsor.human(); + tx[sfSponsorFlags.jsonName] = spfSponsorFee; + tx[sfSponsorSignature.jsonName] = json::ValueType::Object; + tx[sfSponsorSignature.jsonName][sfSigners.jsonName] = json::ValueType::Array; + + json::Value signerObj; + signerObj[sfSigner][jss::Account] = signer.human(); + tx[sfSponsorSignature.jsonName][sfSigners.jsonName].append(signerObj); + + // Leave Fee unset so simulate must autofill it after sponsor signer normalization. + BEAST_EXPECT(!tx.isMember(jss::Fee)); + testTx(env, tx, validateOutput, false); + } + void testTransactionSigningFailure() { @@ -1195,6 +1347,7 @@ public: testTransactionNonTecFailure(); testTransactionTecFailure(); testSuccessfulTransactionMultisigned(); + testSuccessfulSponsoredTransactionMultisigned(); testTransactionSigningFailure(); testInvalidSingleAndMultiSigningTransaction(); testMultisignedBadPubKey(); diff --git a/src/test/shamap/SHAMapSync_test.cpp b/src/test/shamap/SHAMapSync_test.cpp deleted file mode 100644 index bb05ec56df..0000000000 --- a/src/test/shamap/SHAMapSync_test.cpp +++ /dev/null @@ -1,177 +0,0 @@ -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include - -namespace xrpl::tests { - -class SHAMapSync_test : public beast::unit_test::Suite -{ -public: - beast::xor_shift_engine eng; - - boost::intrusive_ptr - makeRandomAS() - { - Serializer s; - - for (int d = 0; d < 3; ++d) - s.add32(randInt(eng)); - return makeShamapitem(s.getSHA512Half(), s.slice()); - } - - bool - confuseMap(SHAMap& map, int count) - { - // add a bunch of random states to a map, then remove them - // map should be the same - SHAMapHash const beforeHash = map.getHash(); - - std::list items; - - for (int i = 0; i < count; ++i) - { - auto item = makeRandomAS(); - items.push_back(item->key()); - - if (!map.addItem(SHAMapNodeType::TnAccountState, item)) - { - log << "Unable to add item to map\n"; - return false; - } - } - - for (auto const& item : items) - { - if (!map.delItem(item)) - { - log << "Unable to remove item from map\n"; - return false; - } - } - - if (beforeHash != map.getHash()) - { - log << "Hashes do not match " << beforeHash << " " << map.getHash() << std::endl; - return false; - } - - return true; - } - - void - run() override - { - using beast::Severity; - test::SuiteJournal journal("SHAMapSync_test", *this); - - TestNodeFamily f(journal), f2(journal); - SHAMap source(SHAMapType::FREE, f); - SHAMap destination(SHAMapType::FREE, f2); - - int const items = 10000; - for (int i = 0; i < items; ++i) - { - source.addItem(SHAMapNodeType::TnAccountState, makeRandomAS()); - if (i % 100 == 0) - source.invariants(); - } - - source.invariants(); - BEAST_EXPECT(confuseMap(source, 500)); - source.invariants(); - - source.setImmutable(); - - int count = 0; - source.visitLeaves([&count](auto const& item) { ++count; }); - BEAST_EXPECT(count == items); - - std::vector missingNodes; - source.walkMap(missingNodes, 2048); - BEAST_EXPECT(missingNodes.empty()); - - destination.setSynching(); - - { - std::vector> a; - - BEAST_EXPECT(source.getNodeFat(SHAMapNodeID(), a, randBool(eng), randInt(eng, 2))); - - unexpected(a.empty(), "NodeSize"); - - BEAST_EXPECT(destination.addRootNode(source.getHash(), makeSlice(a[0].second), nullptr) - .isGood()); - } - - do - { - f.clock().advance(std::chrono::seconds(1)); - - // get the list of nodes we know we need - auto nodesMissing = destination.getMissingNodes(2048, nullptr); - - if (nodesMissing.empty()) - break; - - // get as many nodes as possible based on this information - std::vector> b; - - for (auto& it : nodesMissing) - { - // Don't use BEAST_EXPECT here b/c it will be called a - // non-deterministic number of times and the number of tests run - // should be deterministic - if (!source.getNodeFat(it.first, b, randBool(eng), randInt(eng, 2))) - fail("", __FILE__, __LINE__); - } - - // Don't use BEAST_EXPECT here b/c it will be called a - // non-deterministic number of times and the number of tests run - // should be deterministic - if (b.empty()) - fail("", __FILE__, __LINE__); - - for (auto& node : b) - { - // Don't use BEAST_EXPECT here b/c it will be called a - // non-deterministic number of times and the number of tests run - // should be deterministic - if (!destination.addKnownNode(node.first, makeSlice(node.second), nullptr) - .isUseful()) - fail("", __FILE__, __LINE__); - } - } while (true); - - destination.clearSynching(); - - BEAST_EXPECT(source.deepCompare(destination)); - - destination.invariants(); - } -}; - -BEAST_DEFINE_TESTSUITE(SHAMapSync, shamap, xrpl); - -} // namespace xrpl::tests diff --git a/src/test/shamap/SHAMap_test.cpp b/src/test/shamap/SHAMap_test.cpp deleted file mode 100644 index 5ff5ef5e05..0000000000 --- a/src/test/shamap/SHAMap_test.cpp +++ /dev/null @@ -1,427 +0,0 @@ -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace xrpl::tests { - -#ifndef __INTELLISENSE__ -static_assert(std::is_nothrow_destructible{}); -static_assert(!std::is_default_constructible{}); -static_assert(!std::is_copy_constructible{}); -static_assert(!std::is_copy_assignable{}); -static_assert(!std::is_move_constructible{}); -static_assert(!std::is_move_assignable{}); - -static_assert(std::is_nothrow_destructible{}); -static_assert(std::is_copy_constructible{}); -static_assert(std::is_copy_assignable{}); -static_assert(std::is_move_constructible{}); -static_assert(std::is_move_assignable{}); - -static_assert(std::is_nothrow_destructible{}); -static_assert(!std::is_default_constructible{}); -static_assert(!std::is_copy_constructible{}); - -static_assert(std::is_nothrow_destructible{}); -static_assert(std::is_default_constructible{}); -static_assert(std::is_copy_constructible{}); -static_assert(std::is_copy_assignable{}); -static_assert(std::is_move_constructible{}); -static_assert(std::is_move_assignable{}); - -static_assert(std::is_nothrow_destructible{}); -static_assert(std::is_default_constructible{}); -static_assert(std::is_copy_constructible{}); -static_assert(std::is_copy_assignable{}); -static_assert(std::is_move_constructible{}); -static_assert(std::is_move_assignable{}); - -static_assert(std::is_nothrow_destructible{}); -static_assert(!std::is_default_constructible{}); -static_assert(!std::is_copy_constructible{}); -static_assert(!std::is_copy_assignable{}); -static_assert(!std::is_move_constructible{}); -static_assert(!std::is_move_assignable{}); - -static_assert(std::is_nothrow_destructible{}); -static_assert(!std::is_default_constructible{}); -static_assert(!std::is_copy_constructible{}); -static_assert(!std::is_copy_assignable{}); -static_assert(!std::is_move_constructible{}); -static_assert(!std::is_move_assignable{}); - -static_assert(std::is_nothrow_destructible{}); -static_assert(!std::is_default_constructible{}); -static_assert(!std::is_copy_constructible{}); -static_assert(!std::is_copy_assignable{}); -static_assert(!std::is_move_constructible{}); -static_assert(!std::is_move_assignable{}); -#endif - -inline bool -operator==(SHAMapItem const& a, SHAMapItem const& b) -{ - return a.key() == b.key(); -} -inline bool -operator!=(SHAMapItem const& a, SHAMapItem const& b) -{ - return a.key() != b.key(); -} -inline bool -operator==(SHAMapItem const& a, uint256 const& b) -{ - return a.key() == b; -} -inline bool -operator!=(SHAMapItem const& a, uint256 const& b) -{ - return a.key() != b; -} - -class SHAMap_test : public beast::unit_test::Suite -{ -public: - static Buffer - intToVuc(int v) - { - Buffer vuc(32); - std::fill_n(vuc.data(), vuc.size(), static_cast(v)); - return vuc; - } - - void - run() override - { - using beast::Severity; - test::SuiteJournal journal("SHAMap_test", *this); - - run(true, journal); - run(false, journal); - } - - void - run(bool backed, beast::Journal const& journal) - { - if (backed) - { - testcase("add/traverse backed"); - } - else - { - testcase("add/traverse unbacked"); - } - - tests::TestNodeFamily f(journal); - - // h3 and h4 differ only in the leaf, same terminal node (level 19) - constexpr uint256 kH1("092891fe4ef6cee585fdc6fda0e09eb4d386363158ec3321b8123e5a772c6ca7"); - constexpr uint256 kH2("436ccbac3347baa1f1e53baeef1f43334da88f1f6d70d963b833afd6dfa289fe"); - constexpr uint256 kH3("b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"); - constexpr uint256 kH4("b92891fe4ef6cee585fdc6fda2e09eb4d386363158ec3321b8123e5a772c6ca8"); - constexpr uint256 kH5("a92891fe4ef6cee585fdc6fda0e09eb4d386363158ec3321b8123e5a772c6ca7"); - - SHAMap sMap(SHAMapType::FREE, f); - sMap.invariants(); - if (!backed) - sMap.setUnbacked(); - - auto i1 = makeShamapitem(kH1, intToVuc(1)); - auto i2 = makeShamapitem(kH2, intToVuc(2)); - auto i3 = makeShamapitem(kH3, intToVuc(3)); - auto i4 = makeShamapitem(kH4, intToVuc(4)); - auto i5 = makeShamapitem(kH5, intToVuc(5)); - - unexpected(!sMap.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(*i2)), "no add"); - sMap.invariants(); - unexpected(!sMap.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(*i1)), "no add"); - sMap.invariants(); - - auto i = sMap.begin(); - auto e = sMap.end(); - unexpected(i == e || (*i != *i1), "bad traverse"); - ++i; - unexpected(i == e || (*i != *i2), "bad traverse"); - ++i; - unexpected(i != e, "bad traverse"); - sMap.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(*i4)); - sMap.invariants(); - sMap.delItem(i2->key()); - sMap.invariants(); - sMap.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(*i3)); - sMap.invariants(); - i = sMap.begin(); - e = sMap.end(); - unexpected(i == e || (*i != *i1), "bad traverse"); - ++i; - unexpected(i == e || (*i != *i3), "bad traverse"); - ++i; - unexpected(i == e || (*i != *i4), "bad traverse"); - ++i; - unexpected(i != e, "bad traverse"); - - if (backed) - { - testcase("snapshot backed"); - } - else - { - testcase("snapshot unbacked"); - } - - SHAMapHash const mapHash = sMap.getHash(); - std::shared_ptr const map2 = sMap.snapShot(false); - map2->invariants(); - unexpected(sMap.getHash() != mapHash, "bad snapshot"); - unexpected(map2->getHash() != mapHash, "bad snapshot"); - - SHAMap::Delta delta; - BEAST_EXPECT(sMap.compare(*map2, delta, 100)); - BEAST_EXPECT(delta.empty()); - - unexpected(!sMap.delItem(sMap.begin()->key()), "bad mod"); - sMap.invariants(); - unexpected(sMap.getHash() == mapHash, "bad snapshot"); - unexpected(map2->getHash() != mapHash, "bad snapshot"); - - BEAST_EXPECT(sMap.compare(*map2, delta, 100)); - BEAST_EXPECT(delta.size() == 1); - BEAST_EXPECT(delta.begin()->first == kH1); - BEAST_EXPECT(delta.begin()->second.first == nullptr); - BEAST_EXPECT(delta.begin()->second.second->key() == kH1); - - sMap.dump(); - - if (backed) - { - testcase("build/tear backed"); - } - else - { - testcase("build/tear unbacked"); - } - { - static constexpr std::array keys{ - uint256( - "b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e" - "5a772c6ca8"), - uint256( - "b92881fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e" - "5a772c6ca8"), - uint256( - "b92691fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e" - "5a772c6ca8"), - uint256( - "b92791fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e" - "5a772c6ca8"), - uint256( - "b91891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e" - "5a772c6ca8"), - uint256( - "b99891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e" - "5a772c6ca8"), - uint256( - "f22891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e" - "5a772c6ca8"), - uint256( - "292891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e" - "5a772c6ca8")}; - - static constexpr std::array kHashes{ - uint256( - "B7387CFEA0465759ADC718E8C42B52D2309D179B326E239EB5075C" - "64B6281F7F"), - uint256( - "FBC195A9592A54AB44010274163CB6BA95F497EC5BA0A883184546" - "7FB2ECE266"), - uint256( - "4E7D2684B65DFD48937FFB775E20175C43AF0C94066F7D5679F51A" - "E756795B75"), - uint256( - "7A2F312EB203695FFD164E038E281839EEF06A1B99BFC263F3CECC" - "6C74F93E07"), - uint256( - "395A6691A372387A703FB0F2C6D2C405DAF307D0817F8F0E207596" - "462B0E3A3E"), - uint256( - "D044C0A696DE3169CC70AE216A1564D69DE96582865796142CE7D9" - "8A84D9DDE4"), - uint256( - "76DCC77C4027309B5A91AD164083264D70B77B5E43E08AEDA5EBF9" - "4361143615"), - uint256( - "DF4220E93ADC6F5569063A01B4DC79F8DB9553B6A3222ADE23DEA0" - "2BBE7230E5")}; - - SHAMap map(SHAMapType::FREE, f); - if (!backed) - map.setUnbacked(); - - BEAST_EXPECT(map.getHash() == beast::kZero); - for (int k = 0; k < keys.size(); ++k) - { - BEAST_EXPECT(map.addItem( - SHAMapNodeType::TnTransactionNm, makeShamapitem(keys[k], intToVuc(k)))); - BEAST_EXPECT(map.getHash().asUInt256() == kHashes[k]); - map.invariants(); - } - for (int k = keys.size() - 1; k >= 0; --k) - { - BEAST_EXPECT(map.getHash().asUInt256() == kHashes[k]); - BEAST_EXPECT(map.delItem(keys[k])); - map.invariants(); - } - BEAST_EXPECT(map.getHash() == beast::kZero); - } - - if (backed) - { - testcase("iterate backed"); - } - else - { - testcase("iterate unbacked"); - } - - { - static constexpr std::array keys{ - uint256( - "f22891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e" - "5a772c6ca8"), - uint256( - "b99891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e" - "5a772c6ca8"), - uint256( - "b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e" - "5a772c6ca8"), - uint256( - "b92881fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e" - "5a772c6ca8"), - uint256( - "b92791fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e" - "5a772c6ca8"), - uint256( - "b92691fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e" - "5a772c6ca8"), - uint256( - "b91891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e" - "5a772c6ca8"), - uint256( - "292891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e" - "5a772c6ca8")}; - - tests::TestNodeFamily tf{journal}; - SHAMap map{SHAMapType::FREE, tf}; - if (!backed) - map.setUnbacked(); - for (auto const& k : keys) - { - map.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(k, intToVuc(0))); - map.invariants(); - } - - int h = 7; - for (auto const& k : map) - { - BEAST_EXPECT(k.key() == keys[h]); - --h; - } - } - } -}; - -class SHAMapPathProof_test : public beast::unit_test::Suite -{ - void - run() override - { - test::SuiteJournal journal("SHAMapPathProof_test", *this); - - tests::TestNodeFamily tf{journal}; - SHAMap map{SHAMapType::FREE, tf}; - map.setUnbacked(); - - uint256 key; - uint256 rootHash; - std::vector goodPath; - - for (unsigned char c = 1; c < 100; ++c) - { - uint256 k(c); - map.addItem( - SHAMapNodeType::TnAccountState, makeShamapitem(k, Slice{k.data(), k.size()})); - map.invariants(); - - auto root = map.getHash().asUInt256(); - auto path = map.getProofPath(k); - BEAST_EXPECT(path); - if (!path) - break; - BEAST_EXPECT(map.verifyProofPath(root, k, *path)); - if (c == 1) - { - // extra node - path->insert(path->begin(), path->front()); - BEAST_EXPECT(!map.verifyProofPath(root, k, *path)); - // wrong key - uint256 const wrongKey(c + 1); - BEAST_EXPECT(!map.getProofPath(wrongKey)); - } - if (c == 99) - { - key = k; - rootHash = root; - goodPath = std::move(*path); - } - } - - // still good - BEAST_EXPECT(map.verifyProofPath(rootHash, key, goodPath)); - // empty path - std::vector badPath; - BEAST_EXPECT(!map.verifyProofPath(rootHash, key, badPath)); - // too long - badPath = goodPath; - badPath.push_back(goodPath.back()); - BEAST_EXPECT(!map.verifyProofPath(rootHash, key, badPath)); - // bad node - badPath.clear(); - badPath.emplace_back(100, 100); - BEAST_EXPECT(!map.verifyProofPath(rootHash, key, badPath)); - // bad node type - badPath.clear(); - badPath.push_back(goodPath.front()); - badPath.front().back()--; // change node type - BEAST_EXPECT(!map.verifyProofPath(rootHash, key, badPath)); - // all inner - badPath.clear(); - badPath = goodPath; - badPath.erase(badPath.begin()); - BEAST_EXPECT(!map.verifyProofPath(rootHash, key, badPath)); - } -}; - -BEAST_DEFINE_TESTSUITE(SHAMap, shamap, xrpl); -BEAST_DEFINE_TESTSUITE(SHAMapPathProof, shamap, xrpl); -} // namespace xrpl::tests diff --git a/src/test/unit_test/FileDirGuard.h b/src/test/unit_test/FileDirGuard.h index 3fc5d015b0..b583f821a4 100644 --- a/src/test/unit_test/FileDirGuard.h +++ b/src/test/unit_test/FileDirGuard.h @@ -15,8 +15,8 @@ namespace xrpl::detail { /** - Create a directory and remove it when it's done -*/ + * Create a directory and remove it when it's done + */ class DirGuard { protected: @@ -93,8 +93,8 @@ public: }; /** - Write a file in a directory and remove when done -*/ + * Write a file in a directory and remove when done + */ class FileDirGuard : public DirGuard { protected: diff --git a/src/test/unit_test/multi_runner.h b/src/test/unit_test/multi_runner.h index 3390014cb8..c17fdf3bf0 100644 --- a/src/test/unit_test/multi_runner.h +++ b/src/test/unit_test/multi_runner.h @@ -199,7 +199,8 @@ namespace test { //------------------------------------------------------------------------------ -/** Manager for children running unit tests +/** + * Manager for children running unit tests */ class MultiRunnerParent : private detail::MultiRunnerBase { @@ -234,7 +235,8 @@ public: //------------------------------------------------------------------------------ -/** A class to run a subset of unit tests +/** + * A class to run a subset of unit tests */ class MultiRunnerChild : public beast::unit_test::Runner, private detail::MultiRunnerBase diff --git a/src/test/unit_test/utils.h b/src/test/unit_test/utils.h index 677bbff31b..d4b1e5f7f4 100644 --- a/src/test/unit_test/utils.h +++ b/src/test/unit_test/utils.h @@ -6,9 +6,11 @@ namespace xrpl::test { -/// Compare two SecretKey objects for equality. -/// SecretKey::operator== is deleted, so a named function is used -/// to avoid member-function lookup shadowing free-function overloads. +/** + * Compare two SecretKey objects for equality. + * SecretKey::operator== is deleted, so a named function is used + * to avoid member-function lookup shadowing free-function overloads. + */ inline bool equal(SecretKey const& lhs, SecretKey const& rhs) { diff --git a/src/tests/libxrpl/CMakeLists.txt b/src/tests/libxrpl/CMakeLists.txt index df483a36d8..cafe72eff9 100644 --- a/src/tests/libxrpl/CMakeLists.txt +++ b/src/tests/libxrpl/CMakeLists.txt @@ -29,6 +29,8 @@ set(test_modules basics crypto json + resource + shamap tx protocol_autogen ) diff --git a/src/tests/libxrpl/basics/Buffer.cpp b/src/tests/libxrpl/basics/Buffer.cpp new file mode 100644 index 0000000000..5b34585fcf --- /dev/null +++ b/src/tests/libxrpl/basics/Buffer.cpp @@ -0,0 +1,260 @@ +#include + +#include + +#include + +#include +#include +#include +#include +#include + +namespace xrpl::test { + +struct BufferTest : public ::testing::Test +{ + static bool + sane(Buffer const& b) + { + if (b.empty()) + return b.data() == nullptr; + + return b.data() != nullptr; + } +}; + +TEST_F(BufferTest, buffer) +{ + std::uint8_t const data[] = {0xa8, 0xa1, 0x38, 0x45, 0x23, 0xec, 0xe4, 0x23, 0x71, 0x6d, 0x2a, + 0x18, 0xb4, 0x70, 0xcb, 0xf5, 0xac, 0x2d, 0x89, 0x4d, 0x19, 0x9c, + 0xf0, 0x2c, 0x15, 0xd1, 0xf9, 0x9b, 0x66, 0xd2, 0x30, 0xd3}; + + Buffer const b0; + EXPECT_TRUE(sane(b0)); + EXPECT_TRUE(b0.empty()); + + Buffer b1{0}; + EXPECT_TRUE(sane(b1)); + EXPECT_TRUE(b1.empty()); + std::memcpy(b1.alloc(16), data, 16); + EXPECT_TRUE(sane(b1)); + EXPECT_FALSE(b1.empty()); + EXPECT_EQ(b1.size(), 16); + + Buffer b2{b1.size()}; + EXPECT_TRUE(sane(b2)); + EXPECT_FALSE(b2.empty()); + EXPECT_EQ(b2.size(), b1.size()); + std::memcpy(b2.data(), data + 16, 16); + + Buffer b3{data, sizeof(data)}; + EXPECT_TRUE(sane(b3)); + EXPECT_FALSE(b3.empty()); + EXPECT_EQ(b3.size(), sizeof(data)); + EXPECT_EQ(std::memcmp(b3.data(), data, b3.size()), 0); + + // Check equality and inequality comparisons. + // For code readability, we want to use general + // EXPECT_TRUE instead of specific EXPECT_EQ etc. + EXPECT_TRUE(b0 == b0); + EXPECT_TRUE(b0 != b1); + EXPECT_TRUE(b1 == b1); + EXPECT_TRUE(b1 != b2); + EXPECT_TRUE(b2 != b3); + + // Check copy constructors and copy assignments: + { + Buffer x{b0}; + EXPECT_EQ(x, b0); + EXPECT_TRUE(sane(x)); + Buffer y{b1}; + EXPECT_EQ(y, b1); + EXPECT_TRUE(sane(y)); + x = b2; + EXPECT_EQ(x, b2); + EXPECT_TRUE(sane(x)); + x = y; + EXPECT_EQ(x, y); + EXPECT_TRUE(sane(x)); + y = b3; + EXPECT_EQ(y, b3); + EXPECT_TRUE(sane(y)); + x = b0; + EXPECT_EQ(x, b0); + EXPECT_TRUE(sane(x)); +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wself-assign-overloaded" +#endif + + x = x; + EXPECT_EQ(x, b0); + EXPECT_TRUE(sane(x)); + y = y; + EXPECT_EQ(y, b3); + EXPECT_TRUE(sane(y)); + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + } + + // Check move constructor & move assignments: + { + static_assert(std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_move_assignable_v); + + { // Move-construct from empty buf + Buffer x; + Buffer const y{std::move(x)}; + EXPECT_TRUE(sane(x)); // NOLINT(bugprone-use-after-move) + EXPECT_TRUE(x.empty()); // NOLINT(bugprone-use-after-move) + EXPECT_TRUE(sane(y)); + EXPECT_TRUE(y.empty()); + EXPECT_EQ(x, y); // NOLINT(bugprone-use-after-move) + } + + { // Move-construct from non-empty buf + Buffer x{b1}; + Buffer const y{std::move(x)}; + EXPECT_TRUE(sane(x)); // NOLINT(bugprone-use-after-move) + EXPECT_TRUE(x.empty()); // NOLINT(bugprone-use-after-move) + EXPECT_TRUE(sane(y)); + EXPECT_EQ(y, b1); + } + + { // Move assign empty buf to empty buf + Buffer x; + Buffer y; + + x = std::move(y); + EXPECT_TRUE(sane(x)); + EXPECT_TRUE(x.empty()); + EXPECT_TRUE(sane(y)); // NOLINT(bugprone-use-after-move) + EXPECT_TRUE(y.empty()); // NOLINT(bugprone-use-after-move) + } + + { // Move assign non-empty buf to empty buf + Buffer x; + Buffer y{b1}; + + x = std::move(y); + EXPECT_TRUE(sane(x)); + EXPECT_EQ(x, b1); + EXPECT_TRUE(sane(y)); // NOLINT(bugprone-use-after-move) + EXPECT_TRUE(y.empty()); // NOLINT(bugprone-use-after-move) + } + + { // Move assign empty buf to non-empty buf + Buffer x{b1}; + Buffer y; + + x = std::move(y); + EXPECT_TRUE(sane(x)); + EXPECT_TRUE(x.empty()); + EXPECT_TRUE(sane(y)); // NOLINT(bugprone-use-after-move) + EXPECT_TRUE(y.empty()); // NOLINT(bugprone-use-after-move) + } + + { // Move assign non-empty buf to non-empty buf + Buffer x{b1}; + Buffer y{b2}; + Buffer z{b3}; + + x = std::move(y); + EXPECT_TRUE(sane(x)); + EXPECT_FALSE(x.empty()); + EXPECT_TRUE(sane(y)); // NOLINT(bugprone-use-after-move) + EXPECT_TRUE(y.empty()); // NOLINT(bugprone-use-after-move) + + x = std::move(z); + EXPECT_TRUE(sane(x)); + EXPECT_FALSE(x.empty()); + EXPECT_TRUE(sane(z)); // NOLINT(bugprone-use-after-move) + EXPECT_TRUE(z.empty()); // NOLINT(bugprone-use-after-move) + } + } + + { + Buffer w{static_cast(b0)}; + EXPECT_TRUE(sane(w)); + EXPECT_EQ(w, b0); + + Buffer x{static_cast(b1)}; + EXPECT_TRUE(sane(x)); + EXPECT_EQ(x, b1); + + Buffer y{static_cast(b2)}; + EXPECT_TRUE(sane(y)); + EXPECT_EQ(y, b2); + + Buffer z{static_cast(b3)}; + EXPECT_TRUE(sane(z)); + EXPECT_EQ(z, b3); + + // Assign empty slice to empty buffer + w = static_cast(b0); + EXPECT_TRUE(sane(w)); + EXPECT_EQ(w, b0); + + // Assign non-empty slice to empty buffer + w = static_cast(b1); + EXPECT_TRUE(sane(w)); + EXPECT_EQ(w, b1); + + // Assign non-empty slice to non-empty buffer + x = static_cast(b2); + EXPECT_TRUE(sane(x)); + EXPECT_EQ(x, b2); + + // Assign non-empty slice to non-empty buffer + y = static_cast(z); + EXPECT_TRUE(sane(y)); + EXPECT_EQ(y, z); + + // Assign empty slice to non-empty buffer: + z = static_cast(b0); + EXPECT_TRUE(sane(z)); + EXPECT_EQ(z, b0); + } + + { + auto test = [](Buffer const& b, std::size_t i) { + Buffer x{b}; + + // Try to allocate some number of bytes, possibly + // zero (which means clear) and sanity check + x(i); + EXPECT_TRUE(sane(x)); + EXPECT_EQ(x.size(), i); + EXPECT_EQ((x.data() == nullptr), (i == 0)); + + // Try to allocate some more data (always non-zero) + x(i + 1); + EXPECT_TRUE(sane(x)); + EXPECT_EQ(x.size(), i + 1); + EXPECT_NE(x.data(), nullptr); + + // Try to clear: + x.clear(); + EXPECT_TRUE(sane(x)); + EXPECT_TRUE(x.empty()); + EXPECT_EQ(x.data(), nullptr); + + // Try to clear again: + x.clear(); + EXPECT_TRUE(sane(x)); + EXPECT_TRUE(x.empty()); + EXPECT_EQ(x.data(), nullptr); + }; + + for (std::size_t i = 0; i < 16; ++i) + { + test(b0, i); + test(b1, i); + } + } +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/basics/FileUtilities.cpp b/src/tests/libxrpl/basics/FileUtilities.cpp new file mode 100644 index 0000000000..cd24abd696 --- /dev/null +++ b/src/tests/libxrpl/basics/FileUtilities.cpp @@ -0,0 +1,94 @@ +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace xrpl { + +namespace { + +class TempFile +{ +public: + explicit TempFile(boost::filesystem::path file, std::string const& contents) + : dir_( + boost::filesystem::temp_directory_path() / + boost::filesystem::unique_path("xrpl-file-utilities-%%%%-%%%%-%%%%")) + , file_(dir_ / file) + { + boost::filesystem::create_directory(dir_); + + std::ofstream output(file_.string()); + if (!output) + throw std::runtime_error("Unable to create temporary test file"); + + output << contents; + } + + ~TempFile() + { + boost::system::error_code ec; + boost::filesystem::remove(file_, ec); + boost::filesystem::remove(dir_, ec); + } + + [[nodiscard]] boost::filesystem::path const& + file() const + { + return file_; + } + +private: + boost::filesystem::path dir_; + boost::filesystem::path file_; +}; + +} // namespace + +TEST(FileUtilitiesTest, get_file_contents) +{ + using namespace boost::system; + + constexpr char const* kExpectedContents = "This file is very short. That's all we need."; + + TempFile const file("test_file", "This is temporary text that should get overwritten"); + + error_code ec; + auto const path = file.file(); + + writeFileContents(ec, path, kExpectedContents); + EXPECT_FALSE(ec); + + { + // Test with no max + auto const good = getFileContents(ec, path); + EXPECT_FALSE(ec); + EXPECT_EQ(good, kExpectedContents); + } + + { + // Test with large max + auto const good = getFileContents(ec, path, kilobytes(1)); + EXPECT_FALSE(ec); + EXPECT_EQ(good, kExpectedContents); + } + + { + // Test with small max + auto const bad = getFileContents(ec, path, 16); + EXPECT_TRUE(ec && ec.value() == boost::system::errc::file_too_large); + EXPECT_TRUE(bad.empty()); + } +} + +} // namespace xrpl diff --git a/src/tests/libxrpl/basics/IOUAmount.cpp b/src/tests/libxrpl/basics/IOUAmount.cpp new file mode 100644 index 0000000000..46d6d7c5e1 --- /dev/null +++ b/src/tests/libxrpl/basics/IOUAmount.cpp @@ -0,0 +1,243 @@ +#include + +#include +#include + +#include + +#include +#include +#include +#include + +namespace xrpl { + +TEST(IOUAmountTest, zero) +{ + IOUAmount const z(0, 0); + + EXPECT_EQ(z.mantissa(), 0); + EXPECT_EQ(z.exponent(), -100); + EXPECT_FALSE(z); + EXPECT_EQ(z.signum(), 0); + EXPECT_EQ(z, beast::kZero); + + EXPECT_EQ((z + z), z); + EXPECT_EQ((z - z), z); + EXPECT_EQ(z, -z); + + IOUAmount const zz(beast::kZero); + EXPECT_EQ(z, zz); + + // https://github.com/XRPLF/rippled/issues/5170 + IOUAmount const zzz{}; + EXPECT_EQ(zzz, beast::kZero); + // EXPECT_EQ(zzz, zz); +} + +TEST(IOUAmountTest, sig_num) +{ + IOUAmount const neg(-1, 0); + EXPECT_LT(neg.signum(), 0); + + IOUAmount const zer(0, 0); + EXPECT_EQ(zer.signum(), 0); + + IOUAmount const pos(1, 0); + EXPECT_GT(pos.signum(), 0); +} + +TEST(IOUAmountTest, beast_zero) +{ + using beast::kZero; + + { + IOUAmount const z(kZero); + EXPECT_TRUE(z == kZero); + EXPECT_TRUE(z >= kZero); + EXPECT_TRUE(z <= kZero); + EXPECT_FALSE(z != kZero); + EXPECT_FALSE(z > kZero); + EXPECT_FALSE(z < kZero); + } + + { + IOUAmount const neg(-2, 0); + EXPECT_TRUE(neg < kZero); + EXPECT_TRUE(neg <= kZero); + EXPECT_TRUE(neg != kZero); + EXPECT_FALSE(neg == kZero); + } + + { + IOUAmount const pos(2, 0); + EXPECT_TRUE(pos > kZero); + EXPECT_TRUE(pos >= kZero); + EXPECT_TRUE(pos != kZero); + EXPECT_FALSE(pos == kZero); + } +} + +TEST(IOUAmountTest, comparisons) +{ + IOUAmount const n(-2, 0); + IOUAmount const z(0, 0); + IOUAmount const p(2, 0); + // For code readability, we want to use general + // EXPECT_TRUE instead of specific EXPECT_EQ etc. + EXPECT_TRUE(z == z); + EXPECT_TRUE(z >= z); + EXPECT_TRUE(z <= z); + EXPECT_TRUE(z == -z); + // NOLINTBEGIN(misc-redundant-expression) + EXPECT_FALSE(z > z); + EXPECT_FALSE(z < z); + EXPECT_FALSE(z != z); + // NOLINTEND(misc-redundant-expression) + EXPECT_FALSE(z != -z); + + EXPECT_TRUE(n < z); + EXPECT_TRUE(n <= z); + EXPECT_TRUE(n != z); + EXPECT_FALSE(n > z); + EXPECT_FALSE(n >= z); + EXPECT_FALSE(n == z); + + EXPECT_TRUE(p > z); + EXPECT_TRUE(p >= z); + EXPECT_TRUE(p != z); + EXPECT_FALSE(p < z); + EXPECT_FALSE(p <= z); + EXPECT_FALSE(p == z); + + EXPECT_TRUE(n < p); + EXPECT_TRUE(n <= p); + EXPECT_TRUE(n != p); + EXPECT_FALSE(n > p); + EXPECT_FALSE(n >= p); + EXPECT_FALSE(n == p); + + EXPECT_TRUE(p > n); + EXPECT_TRUE(p >= n); + EXPECT_TRUE(p != n); + EXPECT_FALSE(p < n); + EXPECT_FALSE(p <= n); + EXPECT_FALSE(p == n); + + EXPECT_TRUE(p > -p); + EXPECT_TRUE(p >= -p); + EXPECT_TRUE(p != -p); + + EXPECT_TRUE(n < -n); + EXPECT_TRUE(n <= -n); + EXPECT_TRUE(n != -n); +} + +TEST(IOUAmountTest, to_string) +{ + auto test = [](IOUAmount const& n, std::string const& expected) { + auto const result = to_string(n); + std::stringstream ss; + ss << "to_string(" << result << "). Expected: " << expected; + EXPECT_EQ(result, expected) << ss.str(); + }; + + for (auto const mantissaSize : MantissaRange::getAllScales()) + { + NumberMantissaScaleGuard const mg(mantissaSize); + + test(IOUAmount(-2, 0), "-2"); + test(IOUAmount(0, 0), "0"); + test(IOUAmount(2, 0), "2"); + test(IOUAmount(25, -3), "0.025"); + test(IOUAmount(-25, -3), "-0.025"); + test(IOUAmount(25, 1), "250"); + test(IOUAmount(-25, 1), "-250"); + test(IOUAmount(2, 20), "2e20"); + test(IOUAmount(-2, -20), "-2e-20"); + } +} + +TEST(IOUAmountTest, mul_ratio) +{ + /* The range for the mantissa when normalized */ + constexpr std::int64_t kMinMantissa = 1000000000000000ull; + constexpr std::int64_t kMaxMantissa = 9999999999999999ull; + // log(2,maxMantissa) ~ 53.15 + /* The range for the exponent when normalized */ + constexpr int kMinExponent = -96; + constexpr int kMaxExponent = 80; + constexpr auto kMaxUInt = std::numeric_limits::max(); + + { + // multiply by a number that would overflow the mantissa, then + // divide by the same number, and check we didn't lose any value + IOUAmount const bigMan(kMaxMantissa, 0); + EXPECT_EQ(bigMan, mulRatio(bigMan, kMaxUInt, kMaxUInt, true)); + // rounding mode shouldn't matter as the result is exact + EXPECT_EQ(bigMan, mulRatio(bigMan, kMaxUInt, kMaxUInt, false)); + } + { + // Similar test as above, but for negative values + IOUAmount const bigMan(-kMaxMantissa, 0); + EXPECT_EQ(bigMan, mulRatio(bigMan, kMaxUInt, kMaxUInt, true)); + // rounding mode shouldn't matter as the result is exact + EXPECT_EQ(bigMan, mulRatio(bigMan, kMaxUInt, kMaxUInt, false)); + } + + { + // small amounts + IOUAmount const tiny(kMinMantissa, kMinExponent); + // Round up should give the smallest allowable number + EXPECT_EQ(tiny, mulRatio(tiny, 1, kMaxUInt, true)); + EXPECT_EQ(tiny, mulRatio(tiny, kMaxUInt - 1, kMaxUInt, true)); + // rounding down should be zero + EXPECT_EQ(beast::kZero, mulRatio(tiny, 1, kMaxUInt, false)); + EXPECT_EQ(beast::kZero, mulRatio(tiny, kMaxUInt - 1, kMaxUInt, false)); + + // tiny negative numbers + IOUAmount const tinyNeg(-kMinMantissa, kMinExponent); + // Round up should give zero + EXPECT_EQ(beast::kZero, mulRatio(tinyNeg, 1, kMaxUInt, true)); + EXPECT_EQ(beast::kZero, mulRatio(tinyNeg, kMaxUInt - 1, kMaxUInt, true)); + // rounding down should be tiny + EXPECT_EQ(tinyNeg, mulRatio(tinyNeg, 1, kMaxUInt, false)); + EXPECT_EQ(tinyNeg, mulRatio(tinyNeg, kMaxUInt - 1, kMaxUInt, false)); + } + + { // rounding + { + IOUAmount const one(1, 0); + auto const rup = mulRatio(one, kMaxUInt - 1, kMaxUInt, true); + auto const rdown = mulRatio(one, kMaxUInt - 1, kMaxUInt, false); + EXPECT_EQ(rup.mantissa() - rdown.mantissa(), 1); + } + { + IOUAmount const big(kMaxMantissa, kMaxExponent); + auto const rup = mulRatio(big, kMaxUInt - 1, kMaxUInt, true); + auto const rdown = mulRatio(big, kMaxUInt - 1, kMaxUInt, false); + EXPECT_EQ(rup.mantissa() - rdown.mantissa(), 1); + } + + { + IOUAmount const negOne(-1, 0); + auto const rup = mulRatio(negOne, kMaxUInt - 1, kMaxUInt, true); + auto const rdown = mulRatio(negOne, kMaxUInt - 1, kMaxUInt, false); + EXPECT_EQ(rup.mantissa() - rdown.mantissa(), 1); + } + } + + { + // division by zero + IOUAmount const one(1, 0); + EXPECT_ANY_THROW({ mulRatio(one, 1, 0, true); }); + } + + { + // overflow + IOUAmount const big(kMaxMantissa, kMaxExponent); + EXPECT_ANY_THROW({ mulRatio(big, 2, 0, true); }); + } +} + +} // namespace xrpl diff --git a/src/tests/libxrpl/basics/IntrusiveShared.cpp b/src/tests/libxrpl/basics/IntrusiveShared.cpp new file mode 100644 index 0000000000..e798cd1ccc --- /dev/null +++ b/src/tests/libxrpl/basics/IntrusiveShared.cpp @@ -0,0 +1,844 @@ +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include + +#include + +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::tests { + +/* + * Experimentally, we discovered that using std::barrier performs extremely + * poorly (~1 hour vs ~1 minute to run the test suite) in certain macOS + * environments. To unblock our macOS CI pipeline, we replaced std::barrier with a + * custom mutex-based barrier (Barrier) that significantly improves performance + * without compromising correctness. For future reference, if we ever consider + * reintroducing std::barrier, the following configuration is known to exhibit the + * problem: + * + * Model Name: Mac mini + * Model Identifier: Mac14,3 + * Model Number: Z16K000R4LL/A + * Chip: Apple M2 + * Total Number of Cores: 8 (4 performance and 4 efficiency) + * Memory: 24 GB + * System Firmware Version: 11881.41.5 + * OS Loader Version: 11881.1.1 + * Apple clang version 16.0.0 (clang-1600.0.26.3) + * Target: arm64-apple-darwin24.0.0 + * Thread model: posix + * + */ +struct Barrier +{ + std::mutex mtx; + std::condition_variable cv; + int count; + int const initial; + std::size_t generation{0}; + + explicit Barrier(int n) : count(n), initial(n) + { + } + + void + arriveAndWait() + { + std::unique_lock lock(mtx); + auto const currentGeneration = generation; + if (--count == 0) + { + ++generation; + count = initial; + cv.notify_all(); + } + else + { + cv.wait(lock, [&] { return generation != currentGeneration; }); + } + } +}; + +namespace { +enum class TrackedState : std::uint8_t { + Uninitialized, + Alive, + PartiallyDeletedStarted, + PartiallyDeleted, + DeletedStarted, + Deleted +}; + +class TIBase : public IntrusiveRefCounts +{ +public: + static constexpr std::size_t kMaxStates = 128; + static std::array, kMaxStates> state; + static std::atomic nextId; + static TrackedState + getState(std::size_t id) + { + if (id >= state.size()) + throw std::out_of_range("TIBase state id out of range"); + + return state[id].load(std::memory_order_acquire); + } + static void + resetStates(bool resetCallback) + { + for (std::size_t i = 0; i < kMaxStates; ++i) + { + state[i].store(TrackedState::Uninitialized, std::memory_order_release); + } + nextId.store(0, std::memory_order_release); + if (resetCallback) + TIBase::tracingCallback = [](TrackedState, std::optional) {}; + } + + struct ResetStatesGuard + { + bool resetCallback{false}; + + ResetStatesGuard(bool resetCallback) : resetCallback{resetCallback} + { + TIBase::resetStates(resetCallback); + } + ~ResetStatesGuard() + { + TIBase::resetStates(resetCallback); + } + }; + + TIBase() : id{checkoutID()} + { + state[id].store(TrackedState::Alive, std::memory_order_relaxed); + } + ~TIBase() override + { + using enum TrackedState; + + tracingCallback(state[id].load(std::memory_order_relaxed), DeletedStarted); + + // Use relaxed memory order to try to avoid atomic operations from + // adding additional memory synchronizations that may hide threading + // errors in the underlying shared pointer class. + state[id].store(DeletedStarted, std::memory_order_relaxed); + + tracingCallback(DeletedStarted, Deleted); + + state[id].store(TrackedState::Deleted, std::memory_order_relaxed); + + tracingCallback(TrackedState::Deleted, std::nullopt); + } + + void + partialDestructor() const + { + using enum TrackedState; + + tracingCallback(state[id].load(std::memory_order_relaxed), PartiallyDeletedStarted); + + state[id].store(PartiallyDeletedStarted, std::memory_order_relaxed); + + tracingCallback(PartiallyDeletedStarted, PartiallyDeleted); + + state[id].store(PartiallyDeleted, std::memory_order_relaxed); + + tracingCallback(PartiallyDeleted, std::nullopt); + } + + static std::function)> tracingCallback; + + std::size_t const id; + +private: + static std::size_t + checkoutID() + { + auto const id = nextId.fetch_add(1, std::memory_order_acq_rel); + if (id >= state.size()) + throw std::out_of_range("TIBase state capacity exceeded"); + + return id; + } +}; + +std::array, TIBase::kMaxStates> TIBase::state; +std::atomic TIBase::nextId{0}; + +std::function)> TIBase::tracingCallback = + [](TrackedState, std::optional) {}; + +} // namespace + +TEST(IntrusiveSharedTest, basics) +{ + { + TIBase::ResetStatesGuard const rsg{true}; + + TIBase const b; + EXPECT_EQ(b.useCount(), 1); + b.addWeakRef(); + EXPECT_EQ(b.useCount(), 1); + auto s = b.releaseStrongRef(); + EXPECT_EQ(s, ReleaseStrongRefAction::PartialDestroy); + EXPECT_EQ(b.useCount(), 0); + TIBase const* pb = &b; + partialDestructorFinished(&pb); + EXPECT_FALSE(pb); + auto w = b.releaseWeakRef(); + EXPECT_EQ(w, ReleaseWeakRefAction::Destroy); + } + + std::vector> strong; + std::vector> weak; + { + TIBase::ResetStatesGuard const rsg{true}; + + using enum TrackedState; + auto b = makeSharedIntrusive(); + auto id = b->id; + EXPECT_EQ(TIBase::getState(id), Alive); + EXPECT_EQ(b->useCount(), 1); + for (int i = 0; i < 10; ++i) + { + strong.push_back(b); + } + b.reset(); + EXPECT_EQ(TIBase::getState(id), Alive); + strong.resize(strong.size() - 1); + EXPECT_EQ(TIBase::getState(id), Alive); + strong.clear(); + EXPECT_EQ(TIBase::getState(id), Deleted); + + b = makeSharedIntrusive(); + id = b->id; + EXPECT_EQ(TIBase::getState(id), Alive); + EXPECT_EQ(b->useCount(), 1); + for (int i = 0; i < 10; ++i) + { + weak.emplace_back(b); + EXPECT_EQ(b->useCount(), 1); + } + EXPECT_EQ(TIBase::getState(id), Alive); + weak.resize(weak.size() - 1); + EXPECT_EQ(TIBase::getState(id), Alive); + b.reset(); + EXPECT_EQ(TIBase::getState(id), PartiallyDeleted); + while (!weak.empty()) + { + weak.resize(weak.size() - 1); + if (!weak.empty()) + { + EXPECT_EQ(TIBase::getState(id), PartiallyDeleted); + } + } + EXPECT_EQ(TIBase::getState(id), Deleted); + } + { + TIBase::ResetStatesGuard const rsg{true}; + + using enum TrackedState; + auto b = makeSharedIntrusive(); + auto id = b->id; + EXPECT_EQ(TIBase::getState(id), Alive); + WeakIntrusive w{b}; + EXPECT_EQ(TIBase::getState(id), Alive); + auto s = w.lock(); + EXPECT_TRUE(s && s->useCount() == 2); + b.reset(); + EXPECT_TRUE(TIBase::getState(id) == Alive); + EXPECT_TRUE(s && s->useCount() == 1); + s.reset(); + EXPECT_EQ(TIBase::getState(id), PartiallyDeleted); + EXPECT_TRUE(w.expired()); + s = w.lock(); + // Cannot convert a weak pointer to a strong pointer if object is + // already partially deleted + EXPECT_FALSE(s); + w.reset(); + EXPECT_EQ(TIBase::getState(id), Deleted); + } + { + TIBase::ResetStatesGuard const rsg{true}; + + using enum TrackedState; + using swu = SharedWeakUnion; + swu b = makeSharedIntrusive(); + EXPECT_TRUE(b.isStrong() && b.useCount() == 1); + auto id = b.get()->id; + EXPECT_EQ(TIBase::getState(id), Alive); + swu w = b; + EXPECT_TRUE(TIBase::getState(id) == Alive); + EXPECT_TRUE(w.isStrong() && b.useCount() == 2); + w.convertToWeak(); + EXPECT_TRUE(w.isWeak() && b.useCount() == 1); + swu s = w; + EXPECT_TRUE(s.isWeak() && b.useCount() == 1); + s.convertToStrong(); + EXPECT_TRUE(s.isStrong() && b.useCount() == 2); + b.reset(); + EXPECT_EQ(TIBase::getState(id), Alive); + EXPECT_EQ(s.useCount(), 1); + EXPECT_FALSE(w.expired()); + s.reset(); + EXPECT_EQ(TIBase::getState(id), PartiallyDeleted); + EXPECT_TRUE(w.expired()); + w.convertToStrong(); + // Cannot convert a weak pointer to a strong pointer if object is + // already partially deleted + EXPECT_TRUE(w.isWeak()); + w.reset(); + EXPECT_EQ(TIBase::getState(id), Deleted); + } + { + // Testing SharedWeakUnion assignment operator + + TIBase::ResetStatesGuard const rsg{true}; + + auto strong1 = makeSharedIntrusive(); + auto strong2 = makeSharedIntrusive(); + + auto id1 = strong1->id; + auto id2 = strong2->id; + + EXPECT_NE(id1, id2); + + SharedWeakUnion union1 = strong1; + SharedWeakUnion union2 = strong2; + + EXPECT_TRUE(union1.isStrong()); + EXPECT_TRUE(union2.isStrong()); + EXPECT_EQ(union1.get(), strong1.get()); + EXPECT_EQ(union2.get(), strong2.get()); + + // 1) Normal assignment: explicitly calls SharedWeakUnion assignment + union1 = union2; + EXPECT_TRUE(union1.isStrong()); + EXPECT_TRUE(union2.isStrong()); + EXPECT_EQ(union1.get(), union2.get()); + EXPECT_EQ(TIBase::getState(id1), TrackedState::Alive); + EXPECT_EQ(TIBase::getState(id2), TrackedState::Alive); + + // 2) Test self-assignment + EXPECT_TRUE(union1.isStrong()); + EXPECT_EQ(TIBase::getState(id1), TrackedState::Alive); + int const initialRefCount = strong1->useCount(); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wself-assign-overloaded" + union1 = union1; // Self-assignment +#pragma clang diagnostic pop + EXPECT_TRUE(union1.isStrong()); + EXPECT_EQ(TIBase::getState(id1), TrackedState::Alive); + EXPECT_EQ(strong1->useCount(), initialRefCount); + + // 3) Test assignment from null union pointer + union1 = SharedWeakUnion(); + EXPECT_EQ(union1.get(), nullptr); + + // 4) Test assignment to expired union pointer + strong2.reset(); + union2.reset(); + union1 = union2; + EXPECT_EQ(union1.get(), nullptr); + EXPECT_EQ(TIBase::getState(id2), TrackedState::Deleted); + } +} + +TEST(IntrusiveSharedTest, partial_delete) +{ + // This test creates two threads. One with a strong pointer and one + // with a weak pointer. The strong pointer is reset while the weak + // pointer still holds a reference, triggering a partial delete. + // While the partial delete function runs (a sleep is inserted) the + // weak pointer is reset. The destructor should wait to run until + // after the partial delete function has completed running. + + using enum TrackedState; + + TIBase::ResetStatesGuard const rsg{true}; + + auto strong = makeSharedIntrusive(); + WeakIntrusive weak{strong}; + std::atomic destructorRan{false}; + std::atomic partialDeleteRan{false}; + std::latch partialDeleteStartedSyncPoint{2}; + strong->tracingCallback = [&](TrackedState cur, std::optional next) { + using enum TrackedState; + if (next == DeletedStarted) + { + // strong goes out of scope while weak is still in scope + // This checks that partialDelete has run to completion + // before the destructor is called. A sleep is inserted + // inside the partial delete to make sure the destructor is + // given an opportunity to run during partial delete. + EXPECT_EQ(cur, PartiallyDeleted); + } + if (next == PartiallyDeletedStarted) + { + partialDeleteStartedSyncPoint.arrive_and_wait(); + using namespace std::chrono_literals; + // Sleep and let the weak pointer go out of scope, + // potentially triggering a destructor while partial delete + // is running. The test is to make sure that doesn't happen. + std::this_thread::sleep_for(800ms); + } + if (next == PartiallyDeleted) + { + EXPECT_FALSE(partialDeleteRan.exchange(true) || destructorRan.load()); + } + if (next == Deleted) + { + EXPECT_FALSE(destructorRan.exchange(true)); + } + }; + std::thread t1{[&] { + partialDeleteStartedSyncPoint.arrive_and_wait(); + weak.reset(); // Trigger a full delete as soon as the partial + // delete starts + }}; + std::thread t2{[&] { + strong.reset(); // Trigger a partial delete + }}; + t1.join(); + t2.join(); + + EXPECT_TRUE(destructorRan.load() && partialDeleteRan.load()); +} + +TEST(IntrusiveSharedTest, destructor) +{ + // This test creates two threads. One with a strong pointer and one + // with a weak pointer. The weak pointer is reset while the strong + // pointer still holds a reference. Then the strong pointer is + // reset. Only the destructor should run. The partial destructor + // should not be called. Since the weak reset runs to completion + // before the strong pointer is reset, threading doesn't add much to + // this test, but there is no harm in keeping it. + + using enum TrackedState; + + TIBase::ResetStatesGuard const rsg{true}; + + auto strong = makeSharedIntrusive(); + WeakIntrusive weak{strong}; + std::atomic destructorRan{false}; + std::atomic partialDeleteRan{false}; + std::latch weakResetSyncPoint{2}; + strong->tracingCallback = [&](TrackedState cur, std::optional next) { + using enum TrackedState; + if (next == PartiallyDeleted) + { + EXPECT_FALSE(partialDeleteRan.exchange(true) || destructorRan.load()); + } + if (next == Deleted) + { + EXPECT_FALSE(destructorRan.exchange(true)); + } + }; + std::thread t1{[&] { + weak.reset(); + weakResetSyncPoint.arrive_and_wait(); + }}; + std::thread t2{[&] { + weakResetSyncPoint.arrive_and_wait(); + strong.reset(); // Trigger a partial delete + }}; + t1.join(); + t2.join(); + + EXPECT_TRUE(destructorRan.load() && !partialDeleteRan.load()); +} + +TEST(IntrusiveSharedTest, multithreaded_clear_mixed_variant) +{ + // This test creates and destroys many strong and weak pointers in a + // loop. There is a random mix of strong and weak pointers stored in + // a vector (held as a variant). Both threads clear all the pointers + // and check that the invariants hold. + + using enum TrackedState; + TIBase::ResetStatesGuard const rsg{true}; + + std::atomic destructionState{0}; + // returns destructorRan and partialDestructorRan (in that order) + auto getDestructorState = [&]() -> std::pair { + int const s = destructionState.load(std::memory_order_relaxed); + return {(s & 1) != 0, (s & 2) != 0}; + }; + auto setDestructorRan = [&]() -> void { + destructionState.fetch_or(1, std::memory_order_acq_rel); + }; + auto setPartialDeleteRan = [&]() -> void { + destructionState.fetch_or(2, std::memory_order_acq_rel); + }; + auto tracingCallback = [&](TrackedState cur, std::optional next) { + using enum TrackedState; + auto [destructorRan, partialDeleteRan] = getDestructorState(); + if (next == PartiallyDeleted) + { + EXPECT_FALSE(partialDeleteRan || destructorRan); + setPartialDeleteRan(); + } + if (next == Deleted) + { + EXPECT_FALSE(destructorRan); + setDestructorRan(); + } + }; + auto createVecOfPointers = [&](auto const& toClone, std::default_random_engine& eng) + -> std::vector, WeakIntrusive>> { + std::vector, WeakIntrusive>> result; + std::uniform_int_distribution<> toCreateDist(4, 64); + std::uniform_int_distribution<> isStrongDist(0, 1); + auto numToCreate = toCreateDist(eng); + result.reserve(numToCreate); + for (int i = 0; i < numToCreate; ++i) + { + if (isStrongDist(eng)) + { + result.emplace_back(SharedIntrusive(toClone)); + } + else + { + result.emplace_back(WeakIntrusive(toClone)); + } + } + return result; + }; + constexpr int kLoopIters = 2 * 1024; + constexpr int kNumThreads = 16; + std::vector> toClone; + Barrier loopStartSyncPoint{kNumThreads}; + Barrier postCreateToCloneSyncPoint{kNumThreads}; + Barrier postCreateVecOfPointersSyncPoint{kNumThreads}; + auto engines = [&]() -> std::vector { + std::random_device rd; + std::vector result; + result.reserve(kNumThreads); + for (int i = 0; i < kNumThreads; ++i) + result.emplace_back(rd()); + return result; + }(); + + // cloneAndDestroy clones the strong pointer into a vector of mixed + // strong and weak pointers and destroys them all at once. + // threadId==0 is special. + auto cloneAndDestroy = [&](int threadId) { + for (int i = 0; i < kLoopIters; ++i) + { + // ------ Sync Point ------ + loopStartSyncPoint.arriveAndWait(); + + // only thread 0 should reset the state + std::optional rsg; + if (threadId == 0) + { + // Thread 0 is the genesis thread. It creates the strong + // pointers to be cloned by the other threads. This + // thread will also check that the destructor ran and + // clear the temporary variables. + + rsg.emplace(false); + auto [destructorRan, partialDeleteRan] = getDestructorState(); + EXPECT_TRUE(i == 0 || destructorRan); + destructionState.store(0, std::memory_order_release); + + toClone.clear(); + toClone.resize(kNumThreads); + auto strong = makeSharedIntrusive(); + strong->tracingCallback = tracingCallback; + std::ranges::fill(toClone, strong); + } + + // ------ Sync Point ------ + postCreateToCloneSyncPoint.arriveAndWait(); + + auto v = createVecOfPointers(toClone[threadId], engines[threadId]); + toClone[threadId].reset(); + + // ------ Sync Point ------ + postCreateVecOfPointersSyncPoint.arriveAndWait(); + + v.clear(); + } + }; + std::vector threads; + threads.reserve(kNumThreads); + for (int i = 0; i < kNumThreads; ++i) + { + threads.emplace_back(cloneAndDestroy, i); + } + for (int i = 0; i < kNumThreads; ++i) + { + threads[i].join(); + } +} + +TEST(IntrusiveSharedTest, multithreaded_clear_mixed_union) +{ + // This test creates and destroys many SharedWeak pointers in a + // loop. All the pointers start as strong and a loop randomly + // convert them between strong and weak pointers. Both threads clear + // all the pointers and check that the invariants hold. + // + // Note: This test also differs from the test above in that the pointers + // randomly change from strong to weak and from weak to strong in a + // loop. This can't be done in the variant test above because variant is + // not thread safe while the SharedWeakUnion is thread safe. + + using enum TrackedState; + + TIBase::ResetStatesGuard const rsg{true}; + + std::atomic destructionState{0}; + // returns destructorRan and partialDestructorRan (in that order) + auto getDestructorState = [&]() -> std::pair { + int const s = destructionState.load(std::memory_order_relaxed); + return {(s & 1) != 0, (s & 2) != 0}; + }; + auto setDestructorRan = [&]() -> void { + destructionState.fetch_or(1, std::memory_order_acq_rel); + }; + auto setPartialDeleteRan = [&]() -> void { + destructionState.fetch_or(2, std::memory_order_acq_rel); + }; + auto tracingCallback = [&](TrackedState cur, std::optional next) { + using enum TrackedState; + auto [destructorRan, partialDeleteRan] = getDestructorState(); + if (next == PartiallyDeleted) + { + EXPECT_FALSE(partialDeleteRan || destructorRan); + setPartialDeleteRan(); + } + if (next == Deleted) + { + EXPECT_FALSE(destructorRan); + setDestructorRan(); + } + }; + auto createVecOfPointers = + [&](auto const& toClone, + std::default_random_engine& eng) -> std::vector> { + std::vector> result; + std::uniform_int_distribution<> toCreateDist(4, 64); + auto numToCreate = toCreateDist(eng); + result.reserve(numToCreate); + for (int i = 0; i < numToCreate; ++i) + result.emplace_back(SharedIntrusive(toClone)); + return result; + }; + constexpr int kLoopIters = 2 * 1024; + constexpr int kFlipPointersLoopIters = 256; + constexpr int kNumThreads = 16; + std::vector> toClone; + Barrier loopStartSyncPoint{kNumThreads}; + Barrier postCreateToCloneSyncPoint{kNumThreads}; + Barrier postCreateVecOfPointersSyncPoint{kNumThreads}; + Barrier postFlipPointersLoopSyncPoint{kNumThreads}; + auto engines = [&]() -> std::vector { + std::random_device rd; + std::vector result; + result.reserve(kNumThreads); + for (int i = 0; i < kNumThreads; ++i) + result.emplace_back(rd()); + return result; + }(); + + // cloneAndDestroy clones the strong pointer into a vector of + // mixed strong and weak pointers, runs a loop that randomly + // changes strong pointers to weak pointers, and destroys them + // all at once. + auto cloneAndDestroy = [&](int threadId) { + for (int i = 0; i < kLoopIters; ++i) + { + // ------ Sync Point ------ + loopStartSyncPoint.arriveAndWait(); + + // only thread 0 should reset the state + std::optional rsg; + if (threadId == 0) + { + // threadId 0 is the genesis thread. It creates the + // strong point to be cloned by the other threads. This + // thread will also check that the destructor ran and + // clear the temporary variables. + rsg.emplace(false); + auto [destructorRan, partialDeleteRan] = getDestructorState(); + EXPECT_TRUE(i == 0 || destructorRan); + destructionState.store(0, std::memory_order_release); + + toClone.clear(); + toClone.resize(kNumThreads); + auto strong = makeSharedIntrusive(); + strong->tracingCallback = tracingCallback; + std::ranges::fill(toClone, strong); + } + + // ------ Sync Point ------ + postCreateToCloneSyncPoint.arriveAndWait(); + + auto v = createVecOfPointers(toClone[threadId], engines[threadId]); + toClone[threadId].reset(); + + // ------ Sync Point ------ + postCreateVecOfPointersSyncPoint.arriveAndWait(); + + std::uniform_int_distribution<> isStrongDist(0, 1); + for (int f = 0; f < kFlipPointersLoopIters; ++f) + { + for (auto& p : v) + { + if (isStrongDist(engines[threadId])) + { + p.convertToStrong(); + } + else + { + p.convertToWeak(); + } + } + } + + // ------ Sync Point ------ + postFlipPointersLoopSyncPoint.arriveAndWait(); + + v.clear(); + } + }; + std::vector threads; + threads.reserve(kNumThreads); + for (int i = 0; i < kNumThreads; ++i) + { + threads.emplace_back(cloneAndDestroy, i); + } + for (int i = 0; i < kNumThreads; ++i) + { + threads[i].join(); + } +} + +TEST(IntrusiveSharedTest, multithreaded_locking_weak) +{ + // This test creates a single shared atomic pointer that multiple thread + // create weak pointers from. The threads then lock the weak pointers. + // Both threads clear all the pointers and check that the invariants + // hold. + + using enum TrackedState; + + TIBase::ResetStatesGuard const rsg{true}; + + std::atomic destructionState{0}; + // returns destructorRan and partialDestructorRan (in that order) + auto getDestructorState = [&]() -> std::pair { + int const s = destructionState.load(std::memory_order_relaxed); + return {(s & 1) != 0, (s & 2) != 0}; + }; + auto setDestructorRan = [&]() -> void { + destructionState.fetch_or(1, std::memory_order_acq_rel); + }; + auto setPartialDeleteRan = [&]() -> void { + destructionState.fetch_or(2, std::memory_order_acq_rel); + }; + auto tracingCallback = [&](TrackedState cur, std::optional next) { + using enum TrackedState; + auto [destructorRan, partialDeleteRan] = getDestructorState(); + if (next == PartiallyDeleted) + { + EXPECT_FALSE(partialDeleteRan || destructorRan); + setPartialDeleteRan(); + } + if (next == Deleted) + { + EXPECT_FALSE(destructorRan); + setDestructorRan(); + } + }; + + constexpr int kLoopIters = 2 * 1024; + constexpr int kLockWeakLoopIters = 256; + constexpr int kNumThreads = 16; + std::vector> toLock; + Barrier loopStartSyncPoint{kNumThreads}; + Barrier postCreateToLockSyncPoint{kNumThreads}; + Barrier postLockWeakLoopSyncPoint{kNumThreads}; + + // lockAndDestroy creates weak pointers from the strong pointer + // and runs a loop that locks the weak pointer. At the end of the loop + // all the pointers are destroyed all at once. + auto lockAndDestroy = [&](int threadId) { + for (int i = 0; i < kLoopIters; ++i) + { + // ------ Sync Point ------ + loopStartSyncPoint.arriveAndWait(); + + // only thread 0 should reset the state + std::optional rsg; + if (threadId == 0) + { + // threadId 0 is the genesis thread. It creates the + // strong point to be locked by the other threads. This + // thread will also check that the destructor ran and + // clear the temporary variables. + rsg.emplace(false); + auto [destructorRan, partialDeleteRan] = getDestructorState(); + EXPECT_TRUE(i == 0 || destructorRan); + destructionState.store(0, std::memory_order_release); + + toLock.clear(); + toLock.resize(kNumThreads); + auto strong = makeSharedIntrusive(); + strong->tracingCallback = tracingCallback; + std::ranges::fill(toLock, strong); + } + + // ------ Sync Point ------ + postCreateToLockSyncPoint.arriveAndWait(); + + // Multiple threads all create a weak pointer from the same + // strong pointer + WeakIntrusive const weak{toLock[threadId]}; + for (int wi = 0; wi < kLockWeakLoopIters; ++wi) + { + EXPECT_FALSE(weak.expired()); + auto strong = weak.lock(); + EXPECT_TRUE(strong); + } + + // ------ Sync Point ------ + postLockWeakLoopSyncPoint.arriveAndWait(); + + toLock[threadId].reset(); + } + }; + std::vector threads; + threads.reserve(kNumThreads); + for (int i = 0; i < kNumThreads; ++i) + { + threads.emplace_back(lockAndDestroy, i); + } + for (int i = 0; i < kNumThreads; ++i) + { + threads[i].join(); + } +} + +} // namespace xrpl::tests diff --git a/src/tests/libxrpl/basics/KeyCache.cpp b/src/tests/libxrpl/basics/KeyCache.cpp new file mode 100644 index 0000000000..061264b11d --- /dev/null +++ b/src/tests/libxrpl/basics/KeyCache.cpp @@ -0,0 +1,81 @@ +#include +#include // IWYU pragma: keep +#include +#include +#include + +#include +#include + +#include + +namespace xrpl { + +class KeyCacheTest : public ::testing::Test +{ +public: +}; + +TEST_F(KeyCacheTest, key_cache) +{ + using namespace std::chrono_literals; + TestStopwatch clock; + clock.set(0); + + using Key = std::string; + using Cache = TaggedCache; + + beast::Journal const j{TestSink::instance()}; + + // Insert an item, retrieve it, and age it so it gets purged. + { + Cache c("test", LedgerIndex(1), 2s, clock, j); + + EXPECT_EQ(c.size(), 0); + EXPECT_TRUE(c.insert("one")); + EXPECT_FALSE(c.insert("one")); + EXPECT_EQ(c.size(), 1); + EXPECT_TRUE(c.touchIfExists("one")); + ++clock; + c.sweep(); + EXPECT_EQ(c.size(), 1); + ++clock; + c.sweep(); + EXPECT_EQ(c.size(), 0); + EXPECT_FALSE(c.touchIfExists("one")); + } + + // Insert two items, have one expire + { + Cache c("test", LedgerIndex(2), 2s, clock, j); + + EXPECT_TRUE(c.insert("one")); + EXPECT_EQ(c.size(), 1); + EXPECT_TRUE(c.insert("two")); + EXPECT_EQ(c.size(), 2); + ++clock; + c.sweep(); + EXPECT_EQ(c.size(), 2); + EXPECT_TRUE(c.touchIfExists("two")); + ++clock; + c.sweep(); + EXPECT_EQ(c.size(), 1); + } + + // Insert three items (1 over limit), sweep + { + Cache c("test", LedgerIndex(2), 3s, clock, j); + + EXPECT_TRUE(c.insert("one")); + ++clock; + EXPECT_TRUE(c.insert("two")); + ++clock; + EXPECT_TRUE(c.insert("three")); + ++clock; + EXPECT_EQ(c.size(), 3); + c.sweep(); + EXPECT_LT(c.size(), 3); + } +} + +} // namespace xrpl diff --git a/src/test/basics/Number_test.cpp b/src/tests/libxrpl/basics/Number.cpp similarity index 59% rename from src/test/basics/Number_test.cpp rename to src/tests/libxrpl/basics/Number.cpp index 9383fd6808..70d6be2da5 100644 --- a/src/test/basics/Number_test.cpp +++ b/src/tests/libxrpl/basics/Number.cpp @@ -1,5 +1,5 @@ #include -#include + #include #include #include @@ -10,6 +10,8 @@ #include #include +#include + #include #include #include @@ -27,82 +29,97 @@ namespace xrpl { -class Number_test : public beast::unit_test::Suite +using BigInt = boost::multiprecision::cpp_int; +using Dec = boost::multiprecision::cpp_dec_float_50; + +static std::string +fmt(BigInt const& value) { - using BigInt = boost::multiprecision::cpp_int; - - static std::string - fmt(BigInt const& value) + auto s = to_string(value); + std::string out; + int count = 0; + for (char const& ch : std::views::reverse(s)) { - auto s = to_string(value); - std::string out; - int count = 0; - for (char const& ch : std::views::reverse(s)) - { - if (count != 0 && count % 3 == 0 && (isdigit(ch) != 0)) - out.insert(out.begin(), '_'); - out.insert(out.begin(), ch); - ++count; - } - return out; + if (count != 0 && count % 3 == 0 && (isdigit(ch) != 0)) + out.insert(out.begin(), '_'); + out.insert(out.begin(), ch); + ++count; + } + return out; +} + +BigInt +toBigInt(Number const& n) +{ + BigInt v = n.mantissa(); + auto e = n.exponent(); + + for (; e > 0; --e) + v *= 10; + for (; e < 0; ++e) + { + EXPECT_EQ(v % 10, 0); + v /= 10; + } + return v; +} + +template +static T +pow10(int n) +{ + if (n == 0) + return 1; + if (n == 1) + return 10; + + if (n > 1) + { + auto r = pow10(n / 2); + r *= r; + if (n % 2 != 0) + r *= 10; + return r; } - using dec = boost::multiprecision::cpp_dec_float_50; + T p = 1; + p /= pow10(-n); + return p; +} - template - static T - pow10(int n) +static std::string +fmt(Dec const& value) +{ + std::ostringstream os; + os << std::setprecision(40) << value; + return os.str(); +} + +TEST(NumberTest, zero) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) { - if (n == 0) - return 1; - if (n == 1) - return 10; - - if (n > 1) - { - auto r = pow10(n / 2); - r *= r; - if (n % 2 != 0) - r *= 10; - return r; - } - - // n < 0 - T p = 1; - p /= pow10(-n); - return p; - } - - static std::string - fmt(dec const& v) - { - std::ostringstream os; - os << std::setprecision(40) << v; - return os.str(); - } - -public: - void - testZero() - { - testcase << "zero " << to_string(Number::getMantissaScale()); + NumberMantissaScaleGuard const sg(mantissaScale); for (Number const& z : {Number{0, 0}, Number{0}}) { - BEAST_EXPECT(z.mantissa() == 0); - BEAST_EXPECT(z.exponent() == Number{}.exponent()); + EXPECT_EQ(z.mantissa(), 0); + EXPECT_EQ(z.exponent(), Number{}.exponent()); - BEAST_EXPECT((z + z) == z); - BEAST_EXPECT((z - z) == z); - BEAST_EXPECT(z == -z); + EXPECT_EQ((z + z), z); + EXPECT_EQ((z - z), z); + EXPECT_EQ(z, -z); } } +} - void - testLimits() +TEST(NumberTest, limits) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) { + NumberMantissaScaleGuard const sg(mantissaScale); + auto const scale = Number::getMantissaScale(); - testcase << "test_limits " << to_string(scale); bool caught = false; auto const minMantissa = Number::minMantissa(); try @@ -114,13 +131,13 @@ public: { caught = true; } - BEAST_EXPECT(caught); + EXPECT_TRUE(caught); - auto test = [this](auto const& x, auto const& y, int line) { + auto test = [](auto const& x, auto const& y, int line) { auto const result = x == y; std::stringstream ss; ss << x << " == " << y << " -> " << (result ? "true" : "false"); - expect(result, ss.str(), __FILE__, line); + EXPECT_TRUE(result) << ss.str() << " (" << __FILE__ << ":" << line << ")"; }; test( @@ -164,37 +181,47 @@ public: { caught = true; } - BEAST_EXPECT(caught); + EXPECT_TRUE(caught); } +} - void - testAdd() +TEST(NumberTest, add) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) { - auto const scale = Number::getMantissaScale(); - testcase << "test_add " << to_string(scale); + NumberMantissaScaleGuard const sg(mantissaScale); - using Case = std::tuple; - auto const cSmall = std::to_array( - {{Number{1'000'000'000'000'000, -15}, - Number{6'555'555'555'555'555, -29}, - Number{1'000'000'000'000'066, -15}}, - {Number{-1'000'000'000'000'000, -15}, - Number{-6'555'555'555'555'555, -29}, - Number{-1'000'000'000'000'066, -15}}, - {Number{-1'000'000'000'000'000, -15}, - Number{6'555'555'555'555'555, -29}, - Number{-9'999'999'999'999'344, -16}}, - {Number{-6'555'555'555'555'555, -29}, - Number{1'000'000'000'000'000, -15}, - Number{9'999'999'999'999'344, -16}}, - {Number{}, Number{5}, Number{5}}, - {Number{5}, Number{}, Number{5}}, - {Number{5'555'555'555'555'555, -32768}, - Number{-5'555'555'555'555'554, -32768}, - Number{0}}, - {Number{-9'999'999'999'999'999, -31}, - Number{1'000'000'000'000'000, -15}, - Number{9'999'999'999'999'990, -16}}}); + auto const scale = Number::getMantissaScale(); + + using Case = std::tuple; + auto const cSmall = std::to_array({ + {Number{1'000'000'000'000'000, -15}, + Number{6'555'555'555'555'555, -29}, + Number{1'000'000'000'000'066, -15}, + __LINE__}, + {Number{-1'000'000'000'000'000, -15}, + Number{-6'555'555'555'555'555, -29}, + Number{-1'000'000'000'000'066, -15}, + __LINE__}, + {Number{-1'000'000'000'000'000, -15}, + Number{6'555'555'555'555'555, -29}, + Number{-9'999'999'999'999'344, -16}, + __LINE__}, + {Number{-6'555'555'555'555'555, -29}, + Number{1'000'000'000'000'000, -15}, + Number{9'999'999'999'999'344, -16}, + __LINE__}, + {Number{}, Number{5}, Number{5}, __LINE__}, + {Number{5}, Number{}, Number{5}, __LINE__}, + {Number{5'555'555'555'555'555, -32768}, + Number{-5'555'555'555'555'554, -32768}, + Number{0}, + __LINE__}, + {Number{-9'999'999'999'999'999, -31}, + Number{1'000'000'000'000'000, -15}, + Number{9'999'999'999'999'990, -16}, + __LINE__}, + }); auto const cLarge = std::to_array( // Note that items with extremely large mantissas need to be // calculated, because otherwise they overflow uint64. Items from C @@ -202,45 +229,57 @@ public: { {Number{1'000'000'000'000'000, -15}, Number{6'555'555'555'555'555, -29}, - Number{1'000'000'000'000'065'556, -18}}, + Number{1'000'000'000'000'065'556, -18}, + __LINE__}, {Number{-1'000'000'000'000'000, -15}, Number{-6'555'555'555'555'555, -29}, - Number{-1'000'000'000'000'065'556, -18}}, + Number{-1'000'000'000'000'065'556, -18}, + __LINE__}, {Number{-1'000'000'000'000'000, -15}, Number{6'555'555'555'555'555, -29}, - Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}}, + Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}, + __LINE__}, {Number{-6'555'555'555'555'555, -29}, Number{1'000'000'000'000'000, -15}, - Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}}, - {Number{}, Number{5}, Number{5}}, - {Number{5}, Number{}, Number{5}}, + Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}, + __LINE__}, + {Number{}, Number{5}, Number{5}, __LINE__}, + {Number{5}, Number{}, Number{5}, __LINE__}, {Number{5'555'555'555'555'555'000, -32768}, Number{-5'555'555'555'555'554'000, -32768}, - Number{0}}, + Number{0}, + __LINE__}, {Number{-9'999'999'999'999'999, -31}, Number{1'000'000'000'000'000, -15}, - Number{9'999'999'999'999'990, -16}}, + Number{9'999'999'999'999'990, -16}, + __LINE__}, // Items from cSmall expanded for the larger mantissa {Number{1'000'000'000'000'000'000, -18}, Number{6'555'555'555'555'555'555, -35}, - Number{1'000'000'000'000'000'066, -18}}, + Number{1'000'000'000'000'000'066, -18}, + __LINE__}, {Number{-1'000'000'000'000'000'000, -18}, Number{-6'555'555'555'555'555'555, -35}, - Number{-1'000'000'000'000'000'066, -18}}, + Number{-1'000'000'000'000'000'066, -18}, + __LINE__}, {Number{-1'000'000'000'000'000'000, -18}, Number{6'555'555'555'555'555'555, -35}, - Number{true, 9'999'999'999'999'999'344ULL, -19, Number::Normalized{}}}, + Number{true, 9'999'999'999'999'999'344ULL, -19, Number::Normalized{}}, + __LINE__}, {Number{-6'555'555'555'555'555'555, -35}, Number{1'000'000'000'000'000'000, -18}, - Number{false, 9'999'999'999'999'999'344ULL, -19, Number::Normalized{}}}, - {Number{}, Number{5}, Number{5}}, + Number{false, 9'999'999'999'999'999'344ULL, -19, Number::Normalized{}}, + __LINE__}, + {Number{}, Number{5}, Number{5}, __LINE__}, {Number{5'555'555'555'555'555'555, -32768}, Number{-5'555'555'555'555'555'554, -32768}, - Number{0}}, + Number{0}, + __LINE__}, {Number{true, 9'999'999'999'999'999'999ULL, -37, Number::Normalized{}}, Number{1'000'000'000'000'000'000, -18}, - Number{false, 9'999'999'999'999'999'990ULL, -19, Number::Normalized{}}}, - {Number{Number::kMaxRep - 1}, Number{1, 0}, Number{Number::kMaxRep}}, + Number{false, 9'999'999'999'999'999'990ULL, -19, Number::Normalized{}}, + __LINE__}, + {Number{Number::kMaxRep - 1}, Number{1, 0}, Number{Number::kMaxRep}, __LINE__}, // Test extremes { // Each Number operand rounds up, so the actual mantissa is @@ -248,31 +287,36 @@ public: Number{false, 9'999'999'999'999'999'999ULL, 0, Number::Normalized{}}, Number{false, 9'999'999'999'999'999'999ULL, 0, Number::Normalized{}}, Number{2, 19}, + __LINE__, }, { - // Does not round. Mantissas are going to be > kMaxRep, so if + // Does not round. Mantissas are going to be > maxRep, so if // added together as uint64_t's, the result will overflow. // With addition using uint128_t, there's no problem. After // normalizing, the resulting mantissa ends up less than - // kMaxRep. + // maxRep. Number{false, 9'999'999'999'999'999'990ULL, 0, Number::Normalized{}}, Number{false, 9'999'999'999'999'999'990ULL, 0, Number::Normalized{}}, Number{false, 1'999'999'999'999'999'998ULL, 1, Number::Normalized{}}, + __LINE__, }, }); auto const cLargeLegacy = std::to_array({ - {Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep / 10, 1}}, + {Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep / 10, 1}, __LINE__}, }); auto const cLargeCorrected = std::to_array({ - {Number{Number::kMaxRep}, Number{6, -1}, Number{(Number::kMaxRep / 10) + 1, 1}}, + {Number{Number::kMaxRep}, + Number{6, -1}, + Number{(Number::kMaxRep / 10) + 1, 1}, + __LINE__}, }); - auto test = [this](auto const& c) { - for (auto const& [x, y, z] : c) + auto test = [](auto const& c) { + for (auto const& [x, y, z, line] : c) { auto const result = x + y; std::stringstream ss; ss << x << " + " << y << " = " << result << ". Expected: " << z; - BEAST_EXPECTS(result == z, ss.str()); + EXPECT_EQ(result, z) << ss.str() << " Line: " << line; } }; if (scale == MantissaRange::MantissaScale::Small) @@ -302,31 +346,41 @@ public: { caught = true; } - BEAST_EXPECT(caught); + EXPECT_TRUE(caught); } } +} - void - testSub() +TEST(NumberTest, sub) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) { - auto const scale = Number::getMantissaScale(); - testcase << "test_sub " << to_string(scale); + NumberMantissaScaleGuard const sg(mantissaScale); - using Case = std::tuple; + auto const scale = Number::getMantissaScale(); + + using Case = std::tuple; auto const cSmall = std::to_array( {{Number{1'000'000'000'000'000, -15}, Number{6'555'555'555'555'555, -29}, - Number{9'999'999'999'999'344, -16}}, + Number{9'999'999'999'999'344, -16}, + __LINE__}, {Number{6'555'555'555'555'555, -29}, Number{1'000'000'000'000'000, -15}, - Number{-9'999'999'999'999'344, -16}}, - {Number{1'000'000'000'000'000, -15}, Number{1'000'000'000'000'000, -15}, Number{0}}, + Number{-9'999'999'999'999'344, -16}, + __LINE__}, + {Number{1'000'000'000'000'000, -15}, + Number{1'000'000'000'000'000, -15}, + Number{0}, + __LINE__}, {Number{1'000'000'000'000'000, -15}, Number{1'000'000'000'000'001, -15}, - Number{-1'000'000'000'000'000, -30}}, + Number{-1'000'000'000'000'000, -30}, + __LINE__}, {Number{1'000'000'000'000'001, -15}, Number{1'000'000'000'000'000, -15}, - Number{1'000'000'000'000'000, -30}}}); + Number{1'000'000'000'000'000, -30}, + __LINE__}}); auto const cLarge = std::to_array( // Note that items with extremely large mantissas need to be // calculated, because otherwise they overflow uint64. Items from C @@ -334,49 +388,63 @@ public: { {Number{1'000'000'000'000'000, -15}, Number{6'555'555'555'555'555, -29}, - Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}}, + Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}, + __LINE__}, {Number{6'555'555'555'555'555, -29}, Number{1'000'000'000'000'000, -15}, - Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}}, - {Number{1'000'000'000'000'000, -15}, Number{1'000'000'000'000'000, -15}, Number{0}}, + Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}, + __LINE__}, + {Number{1'000'000'000'000'000, -15}, + Number{1'000'000'000'000'000, -15}, + Number{0}, + __LINE__}, {Number{1'000'000'000'000'000, -15}, Number{1'000'000'000'000'001, -15}, - Number{-1'000'000'000'000'000, -30}}, + Number{-1'000'000'000'000'000, -30}, + __LINE__}, {Number{1'000'000'000'000'001, -15}, Number{1'000'000'000'000'000, -15}, - Number{1'000'000'000'000'000, -30}}, + Number{1'000'000'000'000'000, -30}, + __LINE__}, // Items from cSmall expanded for the larger mantissa {Number{1'000'000'000'000'000'000, -18}, Number{6'555'555'555'555'555'555, -32}, - Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}}, + Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}, + __LINE__}, {Number{6'555'555'555'555'555'555, -32}, Number{1'000'000'000'000'000'000, -18}, - Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}}, + Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}, + __LINE__}, {Number{1'000'000'000'000'000'000, -18}, Number{1'000'000'000'000'000'000, -18}, - Number{0}}, + Number{0}, + __LINE__}, {Number{1'000'000'000'000'000'000, -18}, Number{1'000'000'000'000'000'001, -18}, - Number{-1'000'000'000'000'000'000, -36}}, + Number{-1'000'000'000'000'000'000, -36}, + __LINE__}, {Number{1'000'000'000'000'000'001, -18}, Number{1'000'000'000'000'000'000, -18}, - Number{1'000'000'000'000'000'000, -36}}, - {Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep - 1}}, + Number{1'000'000'000'000'000'000, -36}, + __LINE__}, + {Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep - 1}, __LINE__}, {Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}}, Number{1, 0}, - Number{(Number::kMaxRep / 10) + 1, 1}}, + Number{(Number::kMaxRep / 10) + 1, 1}, + __LINE__}, {Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}}, Number{3, 0}, - Number{Number::kMaxRep}}, - {power(2, 63), Number{3, 0}, Number{Number::kMaxRep}}, + Number{Number::kMaxRep}, + __LINE__}, + {power(2, 63), Number{3, 0}, Number{Number::kMaxRep}, __LINE__}, }); - auto test = [this](auto const& c) { - for (auto const& [x, y, z] : c) + auto test = [](auto const& c) { + for (auto const& [x, y, z, line] : c) { auto const result = x - y; std::stringstream ss; ss << x << " - " << y << " = " << result << ". Expected: " << z; - BEAST_EXPECTS(result == z, ss.str()); + EXPECT_EQ(result, z) << ss.str() << " Line: " << line; } }; if (scale == MantissaRange::MantissaScale::Small) @@ -388,21 +456,24 @@ public: test(cLarge); } } +} - void - testMul() +TEST(NumberTest, mul) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) { + NumberMantissaScaleGuard const sg(mantissaScale); + auto const scale = Number::getMantissaScale(); - testcase << "test_mul " << to_string(scale); using Case = std::tuple; - auto test = [this](auto const& c) { + auto test = [](auto const& c) { for (auto const& [x, y, z] : c) { auto const result = x * y; std::stringstream ss; ss << x << " * " << y << " = " << result << ". Expected: " << z; - BEAST_EXPECTS(result == z, ss.str()); + EXPECT_EQ(result, z) << ss.str(); } }; auto tests = [&](auto const& cSmall, auto const& cLarge) { @@ -484,7 +555,6 @@ public: tests(cSmall, cLarge); } Number::setround(Number::RoundingMode::TowardsZero); - testcase << "test_mul " << to_string(Number::getMantissaScale()) << " towards_zero"; { auto const cSmall = std::to_array( {{Number{7}, Number{8}, Number{56}}, @@ -551,7 +621,6 @@ public: tests(cSmall, cLarge); } Number::setround(Number::RoundingMode::Downward); - testcase << "test_mul " << to_string(Number::getMantissaScale()) << " downward"; { auto const cSmall = std::to_array( {{Number{7}, Number{8}, Number{56}}, @@ -618,7 +687,6 @@ public: tests(cSmall, cLarge); } Number::setround(Number::RoundingMode::Upward); - testcase << "test_mul " << to_string(Number::getMantissaScale()) << " upward"; { auto const cSmall = std::to_array( {{Number{7}, Number{8}, Number{56}}, @@ -684,7 +752,6 @@ public: }); tests(cSmall, cLarge); } - testcase << "test_mul " << to_string(Number::getMantissaScale()) << " overflow"; { bool caught = false; try @@ -696,24 +763,27 @@ public: { caught = true; } - BEAST_EXPECT(caught); + EXPECT_TRUE(caught); } } +} - void - testDiv() +TEST(NumberTest, div) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) { + NumberMantissaScaleGuard const sg(mantissaScale); + auto const scale = Number::getMantissaScale(); - testcase << "test_div " << to_string(scale); using Case = std::tuple; - auto test = [this](auto const& c) { + auto test = [](auto const& c) { for (auto const& [x, y, z] : c) { auto const result = x / y; std::stringstream ss; ss << x << " / " << y << " = " << result << ". Expected: " << z; - BEAST_EXPECTS(result == z, ss.str()); + EXPECT_EQ(result, z) << ss.str(); } }; auto const maxMantissa = Number::maxMantissa(); @@ -764,7 +834,6 @@ public: Number{false, maxMantissa, -18, Number::Normalized{}}}}); tests(cSmall, cLarge); } - testcase << "test_div " << to_string(Number::getMantissaScale()) << " towards_zero"; Number::setround(Number::RoundingMode::TowardsZero); { auto const cSmall = std::to_array( @@ -802,7 +871,6 @@ public: Number{false, maxMantissa, -18, Number::Normalized{}}}}); tests(cSmall, cLarge); } - testcase << "test_div " << to_string(Number::getMantissaScale()) << " downward"; Number::setround(Number::RoundingMode::Downward); { auto const cSmall = std::to_array( @@ -840,7 +908,6 @@ public: Number{false, maxMantissa, -18, Number::Normalized{}}}}); tests(cSmall, cLarge); } - testcase << "test_div " << to_string(Number::getMantissaScale()) << " upward"; Number::setround(Number::RoundingMode::Upward); { auto const cSmall = std::to_array( @@ -878,7 +945,6 @@ public: Number{false, maxMantissa, -18, Number::Normalized{}}}}); tests(cSmall, cLarge); } - testcase << "test_div " << to_string(Number::getMantissaScale()) << " overflow"; bool caught = false; try { @@ -888,29 +954,30 @@ public: { caught = true; } - BEAST_EXPECT(caught); + EXPECT_TRUE(caught); } +} - void - testRoot() +TEST(NumberTest, root) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) { - auto const scale = Number::getMantissaScale(); - testcase << "test_root " << to_string(scale); + NumberMantissaScaleGuard const sg(mantissaScale); using Case = std::tuple; - auto test = [this](auto const& c) { + auto test = [](auto const& c) { for (auto const& [x, y, z] : c) { auto const result = root(x, y); std::stringstream ss; ss << "root(" << x << ", " << y << ") = " << result << ". Expected: " << z; - BEAST_EXPECTS(result == z, ss.str()); + EXPECT_EQ(result, z) << ss.str(); } }; /* auto tests = [&](auto const& cSmall, auto const& cLarge) { test(cSmall); - if (scale != MantissaRange::MantissaScale::Small) + if (scale != MantissaRange::mantissa_scale::small) test(cLarge); }; */ @@ -954,7 +1021,7 @@ public: { caught = true; } - BEAST_EXPECT(caught); + EXPECT_TRUE(caught); caught = false; try { @@ -964,23 +1031,24 @@ public: { caught = true; } - BEAST_EXPECT(caught); + EXPECT_TRUE(caught); } +} - void - testRoot2() +TEST(NumberTest, root2) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) { - auto const scale = Number::getMantissaScale(); - testcase << "test_root2 " << to_string(scale); + NumberMantissaScaleGuard const sg(mantissaScale); - auto test = [this](auto const& c) { + auto test = [](auto const& c) { for (auto const& x : c) { auto const expected = root(x, 2); auto const result = root2(x); std::stringstream ss; ss << "root2(" << x << ") = " << result << ". Expected: " << expected; - BEAST_EXPECTS(result == expected, ss.str()); + EXPECT_EQ(result, expected) << ss.str(); } }; @@ -1005,13 +1073,16 @@ public: { caught = true; } - BEAST_EXPECT(caught); + EXPECT_TRUE(caught); } +} - void - testPower1() +TEST(NumberTest, power1) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) { - testcase << "test_power1 " << to_string(Number::getMantissaScale()); + NumberMantissaScaleGuard const sg(mantissaScale); + using Case = std::tuple; Case const c[]{ {Number{64}, 0, Number{1}}, @@ -1023,13 +1094,16 @@ public: {Number{64}, 11, Number{false, 7378697629483820646ULL, 1, Number::Normalized{}}}, {Number{-64}, 11, Number{true, 7378697629483820646ULL, 1, Number::Normalized{}}}}; for (auto const& [x, y, z] : c) - BEAST_EXPECT((power(x, y) == z)); + EXPECT_EQ(power(x, y), z); } +} - void - testPower2() +TEST(NumberTest, power2) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) { - testcase << "test_power2 " << to_string(Number::getMantissaScale()); + NumberMantissaScaleGuard const sg(mantissaScale); + using Case = std::tuple; Case const c[]{ {Number{1}, 3, 7, Number{1}}, @@ -1039,7 +1113,7 @@ public: {Number{34}, 3, 3, Number{34}}, {Number{4}, 3, 2, Number{8}}}; for (auto const& [x, n, d, z] : c) - BEAST_EXPECT((power(x, n, d) == z)); + EXPECT_EQ(power(x, n, d), z); bool caught = false; try { @@ -1049,7 +1123,7 @@ public: { caught = true; } - BEAST_EXPECT(caught); + EXPECT_TRUE(caught); caught = false; try { @@ -1059,7 +1133,7 @@ public: { caught = true; } - BEAST_EXPECT(caught); + EXPECT_TRUE(caught); caught = false; try { @@ -1069,40 +1143,44 @@ public: { caught = true; } - BEAST_EXPECT(caught); + EXPECT_TRUE(caught); } +} - void - testConversions() +TEST(NumberTest, conversions) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) { - testcase << "testConversions " << to_string(Number::getMantissaScale()); + NumberMantissaScaleGuard const sg(mantissaScale); IOUAmount const x{5, 6}; Number const y = x; - BEAST_EXPECT((y == Number{5, 6})); + EXPECT_EQ(y, (Number{5, 6})); IOUAmount const z{y}; - BEAST_EXPECT(x == z); + EXPECT_EQ(x, z); XRPAmount const xrp{500}; STAmount const st = xrp; Number const n = st; - BEAST_EXPECT(XRPAmount{n} == xrp); + EXPECT_EQ(XRPAmount{n}, xrp); IOUAmount const x0{0, 0}; Number const y0 = x0; - BEAST_EXPECT((y0 == Number{0})); + EXPECT_EQ(y0, Number{0}); IOUAmount const z0{y0}; - BEAST_EXPECT(x0 == z0); + EXPECT_EQ(x0, z0); XRPAmount const xrp0{0}; Number const n0 = xrp0; - BEAST_EXPECT(n0 == Number{0}); - // NOLINTNEXTLINE(misc-confusable-identifiers) - XRPAmount const xrp1{n0}; - BEAST_EXPECT(xrp1 == xrp0); + EXPECT_EQ(n0, Number{0}); + XRPAmount const xrp1{n0}; // NOLINT misc-confusable-identifiers + EXPECT_EQ(xrp1, xrp0); } +} - void - testToInteger() +TEST(NumberTest, to_integer) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) { - testcase << "test_to_integer " << to_string(Number::getMantissaScale()); + NumberMantissaScaleGuard const sg(mantissaScale); + using Case = std::tuple; SaveNumberRoundMode const save{Number::setround(Number::RoundingMode::ToNearest)}; { @@ -1138,11 +1216,11 @@ public: for (auto const& [x, y] : c) { auto j = static_cast(x); - BEAST_EXPECT(j == y); + EXPECT_EQ(j, y); } } auto prevMode = Number::setround(Number::RoundingMode::TowardsZero); - BEAST_EXPECT(prevMode == Number::RoundingMode::ToNearest); + EXPECT_EQ(prevMode, Number::RoundingMode::ToNearest); { Case const c[]{ {Number{0}, 0}, @@ -1176,11 +1254,11 @@ public: for (auto const& [x, y] : c) { auto j = static_cast(x); - BEAST_EXPECT(j == y); + EXPECT_EQ(j, y); } } prevMode = Number::setround(Number::RoundingMode::Downward); - BEAST_EXPECT(prevMode == Number::RoundingMode::TowardsZero); + EXPECT_EQ(prevMode, Number::RoundingMode::TowardsZero); { Case const c[]{ {Number{0}, 0}, @@ -1214,11 +1292,11 @@ public: for (auto const& [x, y] : c) { auto j = static_cast(x); - BEAST_EXPECT(j == y); + EXPECT_EQ(j, y); } } prevMode = Number::setround(Number::RoundingMode::Upward); - BEAST_EXPECT(prevMode == Number::RoundingMode::Downward); + EXPECT_EQ(prevMode, Number::RoundingMode::Downward); { Case const c[]{ {Number{0}, 0}, @@ -1252,7 +1330,7 @@ public: for (auto const& [x, y] : c) { auto j = static_cast(x); - BEAST_EXPECT(j == y); + EXPECT_EQ(j, y); } } bool caught = false; @@ -1264,33 +1342,39 @@ public: { caught = true; } - BEAST_EXPECT(caught); + EXPECT_TRUE(caught); } +} - void - testSquelch() +TEST(NumberTest, squelch) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) { - testcase << "test_squelch " << to_string(Number::getMantissaScale()); + NumberMantissaScaleGuard const sg(mantissaScale); + Number const limit{1, -6}; - BEAST_EXPECT((squelch(Number{2, -6}, limit) == Number{2, -6})); - BEAST_EXPECT((squelch(Number{1, -6}, limit) == Number{1, -6})); - BEAST_EXPECT((squelch(Number{9, -7}, limit) == Number{0})); - BEAST_EXPECT((squelch(Number{-2, -6}, limit) == Number{-2, -6})); - BEAST_EXPECT((squelch(Number{-1, -6}, limit) == Number{-1, -6})); - BEAST_EXPECT((squelch(Number{-9, -7}, limit) == Number{0})); + EXPECT_EQ(squelch(Number{2, -6}, limit), (Number{2, -6})); + EXPECT_EQ(squelch(Number{1, -6}, limit), (Number{1, -6})); + EXPECT_EQ(squelch(Number{9, -7}, limit), Number{0}); + EXPECT_EQ(squelch(Number{-2, -6}, limit), (Number{-2, -6})); + EXPECT_EQ(squelch(Number{-1, -6}, limit), (Number{-1, -6})); + EXPECT_EQ(squelch(Number{-9, -7}, limit), Number{0}); } +} - void - testToString() +TEST(NumberTest, to_string) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) { - auto const scale = Number::getMantissaScale(); - testcase << "testToString " << to_string(scale); + NumberMantissaScaleGuard const sg(mantissaScale); - auto test = [this](Number const& n, std::string const& expected) { + auto const scale = Number::getMantissaScale(); + + auto test = [](Number const& n, std::string const& expected) { auto const result = to_string(n); std::stringstream ss; ss << "to_string(" << result << "). Expected: " << expected; - BEAST_EXPECTS(result == expected, ss.str()); + EXPECT_EQ(result, expected) << ss.str(); }; test(Number(-2, 0), "-2"); @@ -1322,7 +1406,7 @@ public: NumberRoundModeGuard const mg(Number::RoundingMode::TowardsZero); auto const maxMantissa = Number::maxMantissa(); - BEAST_EXPECT(maxMantissa == 9'999'999'999'999'999); + EXPECT_EQ(maxMantissa, (9'999'999'999'999'999)); test( Number{false, (maxMantissa * 1000) + 999, -3, Number::Normalized()}, "9999999999999999"); @@ -1342,8 +1426,7 @@ public: "9223372036854775e3"); } break; - case MantissaRange::MantissaScale::LargeLegacy: - case MantissaRange::MantissaScale::Large: + default: // Test the edges // ((exponent < -(28)) || (exponent > -(8))))) test(Number::min(), "1e-32750"); @@ -1353,7 +1436,7 @@ public: NumberRoundModeGuard const mg(Number::RoundingMode::TowardsZero); auto const maxMantissa = Number::maxMantissa(); - BEAST_EXPECT(maxMantissa == 9'999'999'999'999'999'999ULL); + EXPECT_EQ((maxMantissa), (9'999'999'999'999'999'999ULL)); test( Number{false, maxMantissa, 0, Number::Normalized{}}, "9999999999999999990"); test( @@ -1382,19 +1465,19 @@ public: -(Number{std::numeric_limits::max(), 0} + 1), "-9223372036854775810"); break; - default: - BEAST_EXPECT(false); } } +} - void - testRelationals() +TEST(NumberTest, relationals) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) { - testcase << "test_relationals " << to_string(Number::getMantissaScale()); + NumberMantissaScaleGuard const sg(mantissaScale); { - auto test = [this](auto const& nums) { - BEAST_EXPECT(std::ranges::is_sorted(nums)); + auto test = [](auto const& nums) { + EXPECT_TRUE(std::ranges::is_sorted(nums)); for (auto iter1 = nums.begin(); iter1 != nums.end(); ++iter1) { @@ -1410,32 +1493,32 @@ public: // The ==/!= operators use a completely different code path than <, etc. // This helps detect a breakage in one but not the other. It also helps // verify that the values are being ordered correctly. - BEAST_EXPECTS(smaller != larger, str + " (!=)"); - BEAST_EXPECTS(!(smaller == larger), str + " (==)"); + EXPECT_TRUE(smaller != larger) << str << " (!=)"; + EXPECT_FALSE(smaller == larger) << str << " (==)"; // true results using operator< and derived operators - BEAST_EXPECTS(smaller < larger, str + " (<)"); - BEAST_EXPECTS(larger > smaller, str + " (>)"); - BEAST_EXPECTS(larger >= smaller, str + " (>=)"); - BEAST_EXPECTS(smaller <= larger, str + " (<=)"); + EXPECT_TRUE(smaller < larger) << str << " (<)"; + EXPECT_TRUE(larger > smaller) << str << " (>)"; + EXPECT_TRUE(larger >= smaller) << str << " (>=)"; + EXPECT_TRUE(smaller <= larger) << str << " (<=)"; // false results using operator< and derived operators - BEAST_EXPECTS(!(larger < smaller), str + " (! <)"); - BEAST_EXPECTS(!(smaller > larger), str + " (! >)"); - BEAST_EXPECTS(!(smaller >= larger), str + " (! >=)"); - BEAST_EXPECTS(!(larger <= smaller), str + " (! <=)"); + EXPECT_FALSE(larger < smaller) << str << " (! <)"; + EXPECT_FALSE(smaller > larger) << str << " (! >)"; + EXPECT_FALSE(smaller >= larger) << str << " (! >=)"; + EXPECT_FALSE(larger <= smaller) << str << " (! <=)"; } } }; - auto const intNums = [this]() { + auto const intNums = []() { // Inequality test cases are built from a list of sorted integers auto const values = std::to_array({-100, -50, -20, -10, -1, 0, 1, 10, 20, 50, 100}); // Check this list is sorted before converting it to Numbers. // That way if any of the other tests fail, we know it's because of code and not the // source data. - BEAST_EXPECT(std::ranges::is_sorted(values)); + EXPECT_TRUE(std::ranges::is_sorted(values)); std::vector result; result.reserve(values.size()); @@ -1475,95 +1558,114 @@ public: for (auto const& [n, line] : c) { auto const str = to_string(n); + auto const location = + std::string{" ("} + __FILE__ + ":" + std::to_string(line) + ")"; // NOLINTBEGIN(misc-redundant-expression) Explicitly testing operators with // equivalent values - expect(n == n, str + " ==", __FILE__, line); - expect(!(n != n), str + " !=", __FILE__, line); + EXPECT_TRUE(n == n) << str << " ==" << location; + EXPECT_FALSE(n != n) << str << " !=" << location; - expect(!(n < n), str + " < ", __FILE__, line); - expect(!(n > n), str + " >", __FILE__, line); - expect(n >= n, str + " >=", __FILE__, line); - expect(n <= n, str + " <=", __FILE__, line); + EXPECT_FALSE(n < n) << str << " <" << location; + EXPECT_FALSE(n > n) << str << " >" << location; + EXPECT_TRUE(n >= n) << str << " >=" << location; + EXPECT_TRUE(n <= n) << str << " <=" << location; // NOLINTEND(misc-redundant-expression) } } } +} - void - testStream() +TEST(NumberTest, stream) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) { - testcase << "test_stream " << to_string(Number::getMantissaScale()); + NumberMantissaScaleGuard const sg(mantissaScale); + Number const x{100}; std::ostringstream os; os << x; - BEAST_EXPECT(os.str() == to_string(x)); + EXPECT_EQ((os.str()), (to_string(x))); } +} - void - testIncDec() +TEST(NumberTest, inc_dec) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) { - testcase << "test_inc_dec " << to_string(Number::getMantissaScale()); + NumberMantissaScaleGuard const sg(mantissaScale); + Number x{100}; Number const y = +x; - BEAST_EXPECT(x == y); - BEAST_EXPECT(x++ == y); - BEAST_EXPECT(x == Number{101}); - BEAST_EXPECT(x-- == Number{101}); - BEAST_EXPECT(x == y); + EXPECT_EQ((x), (y)); + EXPECT_EQ((x++), (y)); + EXPECT_EQ((x), (Number{101})); + EXPECT_EQ((x--), (Number{101})); + EXPECT_EQ((x), (y)); } +} - void - testToStAmount() +TEST(NumberTest, to_st_amount) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) { + NumberMantissaScaleGuard const sg(mantissaScale); + Issue const issue; Number const n{7'518'783'80596, -5}; SaveNumberRoundMode const save{Number::setround(Number::RoundingMode::ToNearest)}; auto res2 = STAmount{issue, n}; - BEAST_EXPECT(res2 == STAmount{7518784}); + EXPECT_EQ((res2), (STAmount{7518784})); Number::setround(Number::RoundingMode::TowardsZero); res2 = STAmount{issue, n}; - BEAST_EXPECT(res2 == STAmount{7518783}); + EXPECT_EQ((res2), (STAmount{7518783})); Number::setround(Number::RoundingMode::Downward); res2 = STAmount{issue, n}; - BEAST_EXPECT(res2 == STAmount{7518783}); + EXPECT_EQ((res2), (STAmount{7518783})); Number::setround(Number::RoundingMode::Upward); res2 = STAmount{issue, n}; - BEAST_EXPECT(res2 == STAmount{7518784}); + EXPECT_EQ((res2), (STAmount{7518784})); } +} - void - testTruncate() +TEST(NumberTest, truncate) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) { - BEAST_EXPECT(Number(25, +1).truncate() == Number(250, 0)); - BEAST_EXPECT(Number(25, 0).truncate() == Number(25, 0)); - BEAST_EXPECT(Number(25, -1).truncate() == Number(2, 0)); - BEAST_EXPECT(Number(25, -2).truncate() == Number(0, 0)); - BEAST_EXPECT(Number(99, -2).truncate() == Number(0, 0)); + NumberMantissaScaleGuard const sg(mantissaScale); - BEAST_EXPECT(Number(-25, +1).truncate() == Number(-250, 0)); - BEAST_EXPECT(Number(-25, 0).truncate() == Number(-25, 0)); - BEAST_EXPECT(Number(-25, -1).truncate() == Number(-2, 0)); - BEAST_EXPECT(Number(-25, -2).truncate() == Number(0, 0)); - BEAST_EXPECT(Number(-99, -2).truncate() == Number(0, 0)); + EXPECT_EQ((Number(25, +1).truncate()), (Number(250, 0))); + EXPECT_EQ((Number(25, 0).truncate()), (Number(25, 0))); + EXPECT_EQ((Number(25, -1).truncate()), (Number(2, 0))); + EXPECT_EQ((Number(25, -2).truncate()), (Number(0, 0))); + EXPECT_EQ((Number(99, -2).truncate()), (Number(0, 0))); - BEAST_EXPECT(Number(0, 0).truncate() == Number(0, 0)); - BEAST_EXPECT(Number(0, 30000).truncate() == Number(0, 0)); - BEAST_EXPECT(Number(0, -30000).truncate() == Number(0, 0)); - BEAST_EXPECT(Number(100, -30000).truncate() == Number(0, 0)); - BEAST_EXPECT(Number(100, -30000).truncate() == Number(0, 0)); - BEAST_EXPECT(Number(-100, -30000).truncate() == Number(0, 0)); - BEAST_EXPECT(Number(-100, -30000).truncate() == Number(0, 0)); + EXPECT_EQ((Number(-25, +1).truncate()), (Number(-250, 0))); + EXPECT_EQ((Number(-25, 0).truncate()), (Number(-25, 0))); + EXPECT_EQ((Number(-25, -1).truncate()), (Number(-2, 0))); + EXPECT_EQ((Number(-25, -2).truncate()), (Number(0, 0))); + EXPECT_EQ((Number(-99, -2).truncate()), (Number(0, 0))); + + EXPECT_EQ((Number(0, 0).truncate()), (Number(0, 0))); + EXPECT_EQ((Number(0, 30000).truncate()), (Number(0, 0))); + EXPECT_EQ((Number(0, -30000).truncate()), (Number(0, 0))); + EXPECT_EQ((Number(100, -30000).truncate()), (Number(0, 0))); + EXPECT_EQ((Number(100, -30000).truncate()), (Number(0, 0))); + EXPECT_EQ((Number(-100, -30000).truncate()), (Number(0, 0))); + EXPECT_EQ((Number(-100, -30000).truncate()), (Number(0, 0))); } +} - void - testRounding() +TEST(NumberTest, rounding) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) { + NumberMantissaScaleGuard const sg(mantissaScale); + // Test that rounding works as expected. - testcase("Rounding"); using NumberRoundings = std::map; @@ -1659,348 +1761,703 @@ public: { NumberRoundModeGuard const g{mode}; auto const res = static_cast(num); - BEAST_EXPECTS( - res == val, - to_string(num) + " with mode " + std::to_string(static_cast(mode)) + - " expected " + std::to_string(val) + " got " + std::to_string(res)); + EXPECT_EQ((res), (val)) << to_string(num) + " with mode " + + std::to_string(static_cast(mode)) + " expected " + + std::to_string(val) + " got " + std::to_string(res); } } } +} - void - testInt64() +TEST(NumberTest, int64) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) { + NumberMantissaScaleGuard const sg(mantissaScale); + auto const scale = Number::getMantissaScale(); - testcase << "std::int64_t " << to_string(scale); // Control case - BEAST_EXPECT(Number::maxMantissa() > 10); + EXPECT_GT((Number::maxMantissa()), (10)); Number const ten{10}; - BEAST_EXPECT(ten.exponent() <= 0); + EXPECT_LE((ten.exponent()), (0)); if (scale == MantissaRange::MantissaScale::Small) { - BEAST_EXPECT(std::numeric_limits::max() > kInitialXrp.drops()); - BEAST_EXPECT(Number::maxMantissa() < kInitialXrp.drops()); + EXPECT_GT((std::numeric_limits::max()), (kInitialXrp.drops())); + EXPECT_LT((Number::maxMantissa()), (kInitialXrp.drops())); Number const initalXrp{kInitialXrp}; - BEAST_EXPECT(initalXrp.exponent() > 0); + EXPECT_GT((initalXrp.exponent()), (0)); Number const maxInt64{Number::kMaxRep}; - BEAST_EXPECT(maxInt64.exponent() > 0); + EXPECT_GT((maxInt64.exponent()), (0)); // 85'070'591'730'234'615'865'843'651'857'942'052'864 - 38 digits - BEAST_EXPECT((power(maxInt64, 2) == Number{85'070'591'730'234'62, 22})); + EXPECT_EQ((power(maxInt64, 2)), (Number{85'070'591'730'234'62, 22})); Number const max = Number{false, Number::maxMantissa(), 0, Number::Normalized{}}; - BEAST_EXPECT(max.exponent() <= 0); + EXPECT_LE(max.exponent(), 0); // 99'999'999'999'999'980'000'000'000'000'001 - 32 digits - BEAST_EXPECT((power(max, 2) == Number{99'999'999'999'999'98, 16})); + EXPECT_EQ(power(max, 2), (Number{99'999'999'999'999'98, 16})); } else { - BEAST_EXPECT(std::numeric_limits::max() > kInitialXrp.drops()); - BEAST_EXPECT(Number::maxMantissa() > kInitialXrp.drops()); + EXPECT_GT((std::numeric_limits::max()), (kInitialXrp.drops())); + EXPECT_GT((Number::maxMantissa()), (kInitialXrp.drops())); Number const initalXrp{kInitialXrp}; - BEAST_EXPECT(initalXrp.exponent() <= 0); + EXPECT_LE((initalXrp.exponent()), (0)); Number const maxInt64{Number::kMaxRep}; - BEAST_EXPECT(maxInt64.exponent() <= 0); + EXPECT_LE((maxInt64.exponent()), (0)); // 85'070'591'730'234'615'847'396'907'784'232'501'249 - 38 digits - BEAST_EXPECT((power(maxInt64, 2) == Number{85'070'591'730'234'615'85, 19})); + EXPECT_EQ((power(maxInt64, 2)), (Number{85'070'591'730'234'615'85, 19})); NumberRoundModeGuard const mg(Number::RoundingMode::TowardsZero); auto const maxMantissa = Number::maxMantissa(); Number const max = Number{false, maxMantissa, 0, Number::Normalized{}}; - BEAST_EXPECT(max.mantissa() == maxMantissa / 10); - BEAST_EXPECT(max.exponent() == 1); + EXPECT_EQ((max.mantissa()), (maxMantissa / 10)); + EXPECT_EQ((max.exponent()), (1)); // 99'999'999'999'999'999'800'000'000'000'000'000'100 - also 38 // digits - BEAST_EXPECT( - (power(max, 2) == Number{false, (maxMantissa / 10) - 1, 20, Number::Normalized{}})); + EXPECT_EQ( + (power(max, 2)), (Number{false, (maxMantissa / 10) - 1, 20, Number::Normalized{}})); } } +} - void - testUpwardRoundsDown() +TEST(NumberTest, upward_rounding_produces_value_not_below_exact_at_k_max_rep_cusp) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) { + NumberMantissaScaleGuard const mg{mantissaScale}; + NumberRoundModeGuard const rg{Number::RoundingMode::Upward}; + + auto const scale = Number::getMantissaScale(); + + constexpr std::int64_t kAValue = 1'000'000'000'000'049'863LL; + constexpr std::int64_t kBValue = 9'223'372'036'854'315'903LL; + + Number const a = kAValue; + Number const b = kBValue; + Number const product = a * b; + + // Exact reference in BigInt. + BigInt const exactProduct = BigInt(kAValue) * BigInt(kBValue); + + // What Number actually stored. + BigInt const storedValue = toBigInt(product); + + BigInt const signedDifference = storedValue - exactProduct; + + auto const message = [&] { + std::ostringstream os; + os << "\n" + << " a = " << fmt(BigInt(kAValue)) << "\n" + << " b = " << fmt(BigInt(kBValue)) << "\n" + << " exact a*b = " << fmt(exactProduct) << "\n" + << " stored = " << fmt(storedValue) << "\n" + << " stored - exact = " << fmt(signedDifference) << "\n" + << " upward = " << (signedDifference >= 0 ? "held" : "VIOLATED") << "\n" + << " stored.mantissa = " << product.mantissa() << "\n" + << " stored.exponent = " << product.exponent() << "\n"; + return os.str(); + }; + + switch (scale) + { + case MantissaRange::MantissaScale::Large320: + case MantissaRange::MantissaScale::Large330: + EXPECT_TRUE(signedDifference >= 0) << message(); + EXPECT_TRUE(signedDifference < pow10(product.exponent())) << message(); + EXPECT_EQ(product.mantissa(), (std::numeric_limits::max() / 10) + 1); + EXPECT_EQ(product.exponent(), 19); + break; + + case MantissaRange::MantissaScale::LargeLegacy: + EXPECT_TRUE(signedDifference < 0) << message(); + EXPECT_EQ( + product.mantissa(), (std::numeric_limits::max() / 100) * 100); + EXPECT_EQ(product.exponent(), 18); + break; + + case MantissaRange::MantissaScale::Small: + // The seemingly weird rounding here is because a & b are both + // normalized, and both round up when being converted to Number, + // so you're really getting + // 1_000_000_000_000_050 * 9_223_372_036_854_316. + EXPECT_TRUE(signedDifference >= 0) << message(); + EXPECT_EQ( + product.mantissa(), (std::numeric_limits::max() / 1000) + 3); + EXPECT_EQ(product.exponent(), 21); + break; + } + } +} + +/* + * Companion regression for the kMaxRep cusp behavior, but for `operator/=` on + * the cusp-fix-ENABLED `Large` scale. + * + * Before the dropped-remainder fix, `operator/=` with Upward rounding could + * return a value STRICTLY LESS than the exact quotient, violating Upward's + * directional invariant. + * + * Mechanism (fix-enabled path): + * 1. `operator/=` computes `numerator = nm * 10^17` and + * `zm = numerator / dm` (integer division, truncates remainder). + * 2. If `remainder != 0`, the correction block runs: + * zm *= 100000 + * correction = (remainder * 100000) / dm // also truncates + * zm += correction + * ze -= 5 + * The truncation in `correction` discards a sub-1/100000 residual. + * 3. `normalize`'s shift loop reduces zm to fit, but the discarded residual + * is BELOW the Guard's visibility, so the Guard sees fraction = 0. + * 4. Under Upward + positive, `round()` returns -1 (no round-up), and the + * algorithm returns the truncated zm. + */ +TEST(NumberTest, upward_division_returns_value_not_below_exact_on_large_scale) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) + { + NumberMantissaScaleGuard const mg{mantissaScale}; + NumberRoundModeGuard const rg{Number::RoundingMode::Upward}; + + auto const scale = Number::getMantissaScale(); + + constexpr std::int64_t kAValue = 2LL; + constexpr std::int64_t kBValue = 1'000'000'000'000'000'007LL; + // kBValue = 10^18 + 7 (prime, in [minMantissa, kMaxRep]). + + Number const a{kAValue, 0}; + Number const b{kBValue, 0}; + Number const quotient = a / b; + + Dec const exact = Dec(kAValue) / Dec(kBValue); + Dec const stored = Dec(quotient.mantissa()) * pow10(quotient.exponent()); + Dec const diff = stored - exact; + + auto const message = [&] { + std::ostringstream os; + os << "\n" + << " a = " << kAValue << "\n" + << " b = " << kBValue << "\n" + << " exact a/b = " << fmt(exact) << "\n" + << " stored a/b = " << fmt(stored) << "\n" + << " stored - exact = " << fmt(diff) + << " (negative => Upward gave value BELOW truth)\n" + << " quotient.mantissa = " << quotient.mantissa() << "\n" + << " quotient.exponent = " << quotient.exponent() << "\n"; + return os.str(); + }; + + // Upward invariant: stored >= exact. Bug: stored < exact. + switch (scale) + { + case MantissaRange::MantissaScale::Large320: + case MantissaRange::MantissaScale::Large330: + EXPECT_TRUE(stored >= exact) << message(); + EXPECT_TRUE(diff < pow10(quotient.exponent())) << message(); + break; + + case MantissaRange::MantissaScale::LargeLegacy: + EXPECT_TRUE(stored < exact) << message(); + EXPECT_TRUE(diff >= -pow10(quotient.exponent())) << message(); + break; + + case MantissaRange::MantissaScale::Small: + // Small mantissa doesn't have the correction for dropped remainders. + EXPECT_TRUE(stored < exact) << message(); + break; + } + } +} + +// Companion test case for Upward positive operator/=: Downward negative. +TEST(NumberTest, downward_division_returns_value_not_above_exact_on_large_scale) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) + { + NumberMantissaScaleGuard const mg{mantissaScale}; + NumberRoundModeGuard const rg{Number::RoundingMode::Downward}; + + auto const scale = Number::getMantissaScale(); + + constexpr std::int64_t kAValue = -2LL; + constexpr std::int64_t kBValue = 1'000'000'000'000'000'007LL; + // kBValue = 10^18 + 7 (prime, in [minMantissa, kMaxRep]). + + Number const a{kAValue, 0}; + Number const b{kBValue, 0}; + Number const quotient = a / b; + + Dec const exact = Dec(kAValue) / Dec(kBValue); + Dec const stored = Dec(quotient.mantissa()) * pow10(quotient.exponent()); + Dec const diff = stored - exact; + + auto const message = [&] { + std::ostringstream os; + os << "\n" + << " a = " << kAValue << "\n" + << " b = " << kBValue << "\n" + << " exact a/b = " << fmt(exact) << "\n" + << " stored a/b = " << fmt(stored) << "\n" + << " stored - exact = " << fmt(diff) + << " (positive => Downward gave value ABOVE truth)\n" + << " quotient.mantissa = " << quotient.mantissa() << "\n" + << " quotient.exponent = " << quotient.exponent() << "\n"; + return os.str(); + }; + + // invariant: stored <= exact. Bug: stored > exact. + switch (scale) + { + case MantissaRange::MantissaScale::Large320: + case MantissaRange::MantissaScale::Large330: + EXPECT_TRUE(stored <= exact) << message(); + EXPECT_TRUE(diff > -pow10(quotient.exponent())) << message(); + break; + + case MantissaRange::MantissaScale::LargeLegacy: + EXPECT_TRUE(stored > exact) << message(); + EXPECT_TRUE(diff <= pow10(quotient.exponent())) << message(); + break; + + case MantissaRange::MantissaScale::Small: + // Small mantissa doesn't have the correction for dropped remainders. + EXPECT_TRUE(stored < exact) << message(); + break; + } + } +} + +/* + * Companion test case for Upward positive operator/=: ToNearest. + * + * With ToNearest, if the dropped digits are exactly "5", then the mantissa will + * be rounded to even. The numbers below result in a value where the unrounded + * mantissa ends in an even digit, and "infinite precision" would drop + * "500000000000000000145...", but doNormalize only sees "5". Without the + * rounding fix, doNormalize rounds down to the even value. With the rounding + * fix, doNormalize knows there are more digits beyond "5", and so rounds _up_ + * to the odd value. + */ +TEST(NumberTest, to_nearest_division_uses_dropped_digits_on_large_scale) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) + { + NumberMantissaScaleGuard const mg{mantissaScale}; + NumberRoundModeGuard const rg{Number::RoundingMode::ToNearest}; + + auto const scale = Number::getMantissaScale(); + + constexpr std::int64_t kAValue = 1'269'917'268'816'087'809LL; + constexpr std::int64_t kBValue = 3'458'525'013'821'685'511LL; + // kBValue is prime and in [minMantissa, kMaxRep]. + + Number const a{kAValue, 0}; + Number const b{kBValue, 0}; + Number const quotient = a / b; + + Dec const exact = Dec(kAValue) / Dec(kBValue); + Dec const stored = Dec(quotient.mantissa()) * pow10(quotient.exponent()); + Dec const diff = stored - exact; + + auto const message = [&] { + std::ostringstream os; + os << "\n" + << " a = " << kAValue << "\n" + << " b = " << kBValue << "\n" + << " exact a/b = " << fmt(exact) << "\n" + << " stored a/b = " << fmt(stored) << "\n" + << " stored - exact = " << fmt(diff) + << " (negative => ToNearest gave value BELOW truth)\n" + << " quotient.mantissa = " << quotient.mantissa() << "\n" + << " quotient.exponent = " << quotient.exponent() << "\n"; + return os.str(); + }; + + // invariant: stored >= exact. Bug: stored < exact. + switch (scale) + { + case MantissaRange::MantissaScale::Large320: + case MantissaRange::MantissaScale::Large330: + EXPECT_TRUE(stored >= exact) << message(); + EXPECT_TRUE(diff < pow10(quotient.exponent())) << message(); + break; + + case MantissaRange::MantissaScale::LargeLegacy: + EXPECT_TRUE(stored < exact) << message(); + EXPECT_TRUE(diff >= -pow10(quotient.exponent())) << message(); + break; + + case MantissaRange::MantissaScale::Small: + // Small mantissa doesn't have the correction for dropped remainders. + EXPECT_TRUE(stored < exact) << message(); + break; + } + } +} + +TEST(NumberTest, subtraction_rounding) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) + { + NumberMantissaScaleGuard const mg{mantissaScale}; + NumberRoundModeGuard const rg{Number::RoundingMode::ToNearest}; + + auto const scale = Number::getMantissaScale(); + + auto const exp = Number::mantissaLog(); + // SubCase is + // * offset: offset from exp + // * extraB: whether to include 1e"exp" in "b" + // * aString: expected string value for "a" + // * bString: expected string value for "b" + // There aren't too many valid combinations for test cases here. If extraB is true, + // offset can really only be 2, because any larger and the mantissa can't be represented + // without loss. Offset can't be less than 2, or there's no error. + using SubCase = std::tuple; + auto const c = std::to_array({ + {2, + true, + scale == MantissaRange::MantissaScale::Small ? "100000000000000000" + : "100000000000000000000", + scale == MantissaRange::MantissaScale::Small ? "-1000000000000001" + : "-1000000000000000001"}, + {2, + false, + scale == MantissaRange::MantissaScale::Small ? "100000000000000000" + : "100000000000000000000", + "-1"}, + {30, + false, + scale == MantissaRange::MantissaScale::Small + ? "1000000000000000000000000000000000000000000000" + : "1000000000000000000000000000000000000000000000000", + "-1"}, + }); + + for (auto const& [offset, extraB, aString, bString] : c) + { + Number const a{1LL, exp + offset}; + Number const b{-((extraB ? Number{1, exp} : kNumZero) + 1)}; + + auto const bigA = toBigInt(a); + auto const bigB = toBigInt(b); + + EXPECT_EQ(bigA, BigInt{aString}); + EXPECT_EQ(bigB, BigInt{bString}); + + auto construct = [&a, &b](Number::RoundingMode r) { + NumberRoundModeGuard const roundGuard{r}; + auto const sum = a + b; + BigInt const stored = toBigInt(sum); + return std::make_pair(r, std::make_pair(stored, sum)); + }; + + BigInt const exact = bigA + bigB; + + auto const sums = [&]() { + std::map> r; + r.emplace(construct(Number::RoundingMode::TowardsZero)); + r.emplace(construct(Number::RoundingMode::Upward)); + r.emplace(construct(Number::RoundingMode::Downward)); + r.emplace(construct(Number::RoundingMode::ToNearest)); + return r; + }(); + + auto const message = [&](auto const& r, auto const& sum) { + std::ostringstream os; + os << "\n a = " << a << " (" << fmt(bigA) + << ")\n b = " << b << " (" << fmt(bigB) + << ")\n exact a + b = " << fmt(exact) << "\n"; + + auto const diff = sum.first - exact; + auto const rLabel = to_string(r); + os << std::string(15 - rLabel.length(), ' ') << rLabel << " = " << fmt(sum.first) + << "\n difference = " << fmt(diff) << "\n"; + + return os.str(); + }; + + auto const expectedExponent = + offset - (scale == MantissaRange::MantissaScale::Small && extraB ? 1 : 0); + auto const epsilon = pow10(expectedExponent); + for (auto const& [r, sum] : sums) + { + auto diff = sum.first - exact; + switch (scale) + { + case MantissaRange::MantissaScale::Small: + case MantissaRange::MantissaScale::LargeLegacy: + case MantissaRange::MantissaScale::Large320: { + // Without the fix, all the results but one round up + if (r == Number::RoundingMode::Downward) + { + // Downward works because the Guard sign is negative, and Downward + // returns Up instead of Down if negative and there's a remainder, + // whereas TowardsZero always returns Down. + EXPECT_LT(sum.first, exact) << message(r, sum); + EXPECT_EQ(diff, -(epsilon - 1)) << message(r, sum); + } + else + { + EXPECT_GT(sum.first, exact) << message(r, sum); + EXPECT_EQ(diff, 1) << message(r, sum); + } + break; + } + default: { + EXPECT_LE(sum.second.exponent(), expectedExponent) << message(r, sum); + switch (r) + { + case Number::RoundingMode::Upward: + case Number::RoundingMode::ToNearest: + EXPECT_GT(sum.first, exact) << message(r, sum); + EXPECT_EQ(diff, 1) << message(r, sum); + break; + default: + EXPECT_LT(sum.first, exact) << message(r, sum); + EXPECT_EQ(diff, -(epsilon - 1)) << message(r, sum); + } + } + } + } + } + } +} + +TEST(NumberTest, number_add_directed_sign_wrong) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) + { + NumberMantissaScaleGuard const mg{mantissaScale}; + NumberRoundModeGuard const rg{Number::RoundingMode::ToNearest}; + auto const scale = Number::getMantissaScale(); { - testcase << "upward rounding produces a value below exact at kMaxRep cusp " - << to_string(scale); + // Two negative numbers with the same exponent + Number const a{-6, Number::mantissaLog()}; + Number const b{a - 3}; + EXPECT_TRUE(a.exponent() == b.exponent() && abs(b) > abs(a)); - NumberRoundModeGuard const rg{Number::RoundingMode::Upward}; - - constexpr std::int64_t kAValue = 1'000'000'000'000'049'863LL; - constexpr std::int64_t kBValue = 9'223'372'036'854'315'903LL; - - Number const a = kAValue; - Number const b = kBValue; - Number const product = a * b; - - // Exact reference in BigInt. - BigInt const exactProduct = BigInt(kAValue) * BigInt(kBValue); - - // What Number actually stored. - BigInt storedValue = BigInt(product.mantissa()); - for (int i = 0; i < product.exponent(); ++i) - storedValue *= 10; - - BigInt const signedDifference = storedValue - exactProduct; - - log << "\n" - << " a = " << fmt(BigInt(kAValue)) << "\n" - << " b = " << fmt(BigInt(kBValue)) << "\n" - << " exact a*b = " << fmt(exactProduct) << "\n" - << " stored = " << fmt(storedValue) << "\n" - << " stored - exact = " << fmt(signedDifference) << "\n" - << " upward = " << (signedDifference >= 0 ? "held" : "VIOLATED") << "\n" - << " stored.mantissa = " << product.mantissa() << "\n" - << " stored.exponent = " << product.exponent() << "\n"; - log.flush(); - - switch (scale) + BigInt const exact = toBigInt(a) + toBigInt(b); + if (scale == MantissaRange::MantissaScale::Small) { - case MantissaRange::MantissaScale::Large: - BEAST_EXPECT(signedDifference >= 0); - BEAST_EXPECT(signedDifference < pow10(product.exponent())); - BEAST_EXPECT( - product.mantissa() == (std::numeric_limits::max() / 10) + 1); - BEAST_EXPECT(product.exponent() == 19); - break; + EXPECT_EQ(exact, BigInt{"-12000000000000003"}); + } + else + { + EXPECT_EQ(exact, BigInt{"-12000000000000000003"}); + } - case MantissaRange::MantissaScale::LargeLegacy: - BEAST_EXPECT(signedDifference < 0); - BEAST_EXPECT( - product.mantissa() == - (std::numeric_limits::max() / 100) * 100); - BEAST_EXPECT(product.exponent() == 18); - break; + Number down, up; + { + NumberRoundModeGuard const g{Number::RoundingMode::Downward}; + down = a + b; + } + { + NumberRoundModeGuard const g{Number::RoundingMode::Upward}; + up = a + b; + } - case MantissaRange::MantissaScale::Small: - // The seemingly weird rounding here is because - // a & b are both normalized, and both round up when - // being converted to Number, so you're really - // getting 1_000_000_000_000_050 * 9_223_372_036_854_316 - BEAST_EXPECT(signedDifference >= 0); - BEAST_EXPECT( - product.mantissa() == - (std::numeric_limits::max() / 1000) + 3); - BEAST_EXPECT(product.exponent() == 21); - break; + auto const valueDown = toBigInt(down); + auto const valueUp = toBigInt(up); + auto message = [&] { + std::ostringstream log; + log << " exact = " << fmt(exact) << "\n downward = " << fmt(valueDown) + << " (correct rounding: <= exact)" + << "\n upward = " << fmt(valueUp) << " (correct rounding: >= exact)\n\n"; + return log.str(); + }; + + if (scale == MantissaRange::MantissaScale::Large330) + { + EXPECT_LE(valueDown, exact) << message(); // Downward should round away from zero + EXPECT_GE(valueUp, exact) << message(); // Upward should round toward 0 + } + else + { + EXPECT_GT(valueDown, exact) + << message(); // Downward rounded toward zero (too high) + EXPECT_LT(valueUp, exact) << message(); // Upward rounded toward -inf (too low) } } { - /* Companion regression for the kMaxRep cusp behavior, but for - * `operator/=` on the cusp-fix-ENABLED `Large` scale. - * - * Before the dropped-remainder fix, `operator/=` with Upward - * rounding could return a value STRICTLY LESS than the exact quotient, - * violating Upward's directional invariant. - * - * Mechanism (fix-enabled path): - * 1. `operator/=` computes `numerator = nm * 10^17` and - * `zm = numerator / dm` (integer division, truncates remainder). - * 2. If `remainder != 0`, the correction block runs: - * zm *= 100000 - * correction = (remainder * 100000) / dm // also truncates - * zm += correction - * ze -= 5 - * The truncation in `correction` discards a sub-1/100000 residual. - * 3. `normalize`'s shift loop reduces zm to fit, but the discarded - * residual is BELOW the Guard's visibility, so the Guard sees fraction = 0. - * 4. Under Upward + positive, `round()` returns -1 (no round-up), and - * the algorithm returns the truncated zm - */ - testcase << "operator/= Upward on Large returns value < truth " << to_string(scale); + // Positive control: the same magnitudes with a positive result round + Number const pa{6, Number::mantissaLog()}; + Number const pb{pa + 3}; + EXPECT_TRUE(pa.exponent() == pb.exponent() && abs(pb) > abs(pa)); + BigInt const pexact = toBigInt(pa) + toBigInt(pb); // 12'000'000'000'000'000'003 - NumberRoundModeGuard const roundGuard{Number::RoundingMode::Upward}; - - constexpr std::int64_t aValue = 2LL; - constexpr std::int64_t bValue = 1'000'000'000'000'000'007LL; - // bValue = 10^18 + 7 (prime, in [minMantissa, kMaxRep]). - - Number const a{aValue, 0}; - Number const b{bValue, 0}; - Number const quotient = a / b; - - dec const exact = dec(aValue) / dec(bValue); - dec const stored = dec(quotient.mantissa()) * pow10(quotient.exponent()); - dec const diff = stored - exact; - - log << "\n" - << " a = " << aValue << "\n" - << " b = " << bValue << "\n" - << " exact a/b = " << fmt(exact) << "\n" - << " stored a/b = " << fmt(stored) << "\n" - << " stored - exact = " << fmt(diff) - << " (negative => Upward gave value BELOW truth)\n" - << " quotient.mantissa = " << quotient.mantissa() << "\n" - << " quotient.exponent = " << quotient.exponent() << "\n"; - log.flush(); - - // Upward invariant: stored >= exact. Bug: stored < exact. - switch (scale) + Number pdown, pup; { - case MantissaRange::MantissaScale::Large: - BEAST_EXPECT(stored >= exact); - BEAST_EXPECT(diff < pow10(quotient.exponent())); - break; - - case MantissaRange::MantissaScale::LargeLegacy: - BEAST_EXPECT(stored < exact); - BEAST_EXPECT(diff >= -pow10(quotient.exponent())); - break; - - case MantissaRange::MantissaScale::Small: - // Small mantissa doesn't have the correction for - // dropped remainders - BEAST_EXPECT(stored < exact); - break; + NumberRoundModeGuard const g{Number::RoundingMode::Downward}; + pdown = pa + pb; } + { + NumberRoundModeGuard const g{Number::RoundingMode::Upward}; + pup = pa + pb; + } + auto const valuePDown = toBigInt(pdown); + auto const valuePUp = toBigInt(pup); + auto message = [&] { + std::ostringstream log; + log << " exact = " << fmt(pexact) << "\n downward = " << fmt(valuePDown) + << " (correct rounding: <= exact)" + << "\n upward = " << fmt(valuePUp) + << " (correct rounding: >= exact)\n\n"; + return log.str(); + }; + + EXPECT_LE(valuePDown, pexact) << message(); // correct for positive results + EXPECT_GE(valuePUp, pexact) << message(); } + { - /* Companion test case for Upward positive operator/=: Downward negative - */ - testcase << "operator/= Downward on Large returns value < truth " << to_string(scale); + // Mixed sign numbers with the same exponent: negative second value + Number const a{1, Number::mantissaLog()}; + Number const b{Number{-9, Number::mantissaLog()} - 3}; + EXPECT_TRUE(a.exponent() == b.exponent() && abs(b) > abs(a)); - NumberRoundModeGuard const roundGuard{Number::RoundingMode::Downward}; - - constexpr std::int64_t aValue = -2LL; - constexpr std::int64_t bValue = 1'000'000'000'000'000'007LL; - // bValue = 10^18 + 7 (prime, in [minMantissa, kMaxRep]). - - Number const a{aValue, 0}; - Number const b{bValue, 0}; - Number const quotient = a / b; - - dec const exact = dec(aValue) / dec(bValue); - dec const stored = dec(quotient.mantissa()) * pow10(quotient.exponent()); - dec const diff = stored - exact; - - log << "\n" - << " a = " << aValue << "\n" - << " b = " << bValue << "\n" - << " exact a/b = " << fmt(exact) << "\n" - << " stored a/b = " << fmt(stored) << "\n" - << " stored - exact = " << fmt(diff) - << " (positive => Downward gave value ABOVE truth)\n" - << " quotient.mantissa = " << quotient.mantissa() << "\n" - << " quotient.exponent = " << quotient.exponent() << "\n"; - log.flush(); - - // invariant: stored <= exact. Bug: stored > exact. - switch (scale) + BigInt const exact = toBigInt(a) + toBigInt(b); + if (scale == MantissaRange::MantissaScale::Small) { - case MantissaRange::MantissaScale::Large: - BEAST_EXPECT(stored <= exact); - BEAST_EXPECT(diff > -pow10(quotient.exponent())); - break; - - case MantissaRange::MantissaScale::LargeLegacy: - BEAST_EXPECT(stored > exact); - BEAST_EXPECT(diff <= pow10(quotient.exponent())); - break; - - case MantissaRange::MantissaScale::Small: - // Small mantissa doesn't have the correction for - // dropped remainders - BEAST_EXPECT(stored < exact); - break; + EXPECT_EQ(exact, BigInt{"-8000000000000003"}); } + else + { + EXPECT_EQ(exact, BigInt{"-8000000000000000003"}); + } + + Number down, up; + { + NumberRoundModeGuard const g{Number::RoundingMode::Downward}; + down = a + b; + } + { + NumberRoundModeGuard const g{Number::RoundingMode::Upward}; + up = a + b; + } + + auto const valueDown = toBigInt(down); + auto const valueUp = toBigInt(up); + auto message = [&] { + std::ostringstream log; + log << " exact = " << fmt(exact) << "\n downward = " << fmt(valueDown) + << " (correct rounding: <= exact)" + << "\n upward = " << fmt(valueUp) << " (correct rounding: >= exact)\n\n"; + return log.str(); + }; + + EXPECT_LE(valueDown, exact) << message(); // Downward should round away from zero + EXPECT_GE(valueUp, exact) << message(); // Upward should round toward 0 } + { - /* Companion test case for Upward positive operator/=: ToNearest - * - * With ToNearest, if the dropped digits are exactly "5", then the mantissa will be - * rounded to even. The numbers below result in a value where the unrounded mantissa - * ends in an even digit, and "infinite precision" would drop - * "500000000000000000145...", but doNormalize only sees "5". Without the rounding fix, - * doNormalize rounds down to the even value. With the rounding fix, doNormalize knows - * there are more digits beyond "5", and so rounds _up_ to the odd value. - */ - testcase << "operator/= ToNearest on Large returns value < truth " << to_string(scale); + // Mixed sign numbers with the same exponent: negative first value + Number const a{-1, Number::mantissaLog()}; + Number const b{Number{9, Number::mantissaLog()} + 3}; + EXPECT_TRUE(a.exponent() == b.exponent() && abs(b) > abs(a)); - NumberRoundModeGuard const roundGuard{Number::RoundingMode::ToNearest}; - - constexpr std::int64_t aValue = 1'269'917'268'816'087'809LL; - constexpr std::int64_t bValue = 3'458'525'013'821'685'511LL; - // bValue = 10^18 + 7 (prime, in [minMantissa, kMaxRep]). - - Number const a{aValue, 0}; - Number const b{bValue, 0}; - Number const quotient = a / b; - - dec const exact = dec(aValue) / dec(bValue); - dec const stored = dec(quotient.mantissa()) * pow10(quotient.exponent()); - dec const diff = stored - exact; - - log << "\n" - << " a = " << aValue << "\n" - << " b = " << bValue << "\n" - << " exact a/b = " << fmt(exact) << "\n" - << " stored a/b = " << fmt(stored) << "\n" - << " stored - exact = " << fmt(diff) - << " (negative => ToNearest gave value BELOW truth)\n" - << " quotient.mantissa = " << quotient.mantissa() << "\n" - << " quotient.exponent = " << quotient.exponent() << "\n"; - log.flush(); - - // invariant: stored >= exact. Bug: stored < exact. - switch (scale) + BigInt const exact = toBigInt(a) + toBigInt(b); + if (scale == MantissaRange::MantissaScale::Small) { - case MantissaRange::MantissaScale::Large: - BEAST_EXPECT(stored >= exact); - BEAST_EXPECT(diff < pow10(quotient.exponent())); - break; - - case MantissaRange::MantissaScale::LargeLegacy: - BEAST_EXPECT(stored < exact); - BEAST_EXPECT(diff >= -pow10(quotient.exponent())); - break; - - case MantissaRange::MantissaScale::Small: - // Small mantissa doesn't have the correction for - // dropped remainders - BEAST_EXPECT(stored < exact); - break; + EXPECT_EQ(exact, BigInt{"8000000000000003"}); } + else + { + EXPECT_EQ(exact, BigInt{"8000000000000000003"}); + } + + Number down, up; + { + NumberRoundModeGuard const g{Number::RoundingMode::Downward}; + down = a + b; + } + { + NumberRoundModeGuard const g{Number::RoundingMode::Upward}; + up = a + b; + } + + auto const valueDown = toBigInt(down); + auto const valueUp = toBigInt(up); + auto message = [&] { + std::ostringstream log; + log << " exact = " << fmt(exact) << "\n downward = " << fmt(valueDown) + << " (correct rounding: <= exact)" + << "\n upward = " << fmt(valueUp) << " (correct rounding: >= exact)\n\n"; + return log.str(); + }; + + EXPECT_LE(valueDown, exact) << message(); // Downward should round away from zero + EXPECT_GE(valueUp, exact) << message(); // Upward should round toward 0 } } +} - void - run() override +TEST(NumberTest, number_add_to_nearest_picks_farther) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) { - for (auto const scale : MantissaRange::getAllScales()) - { - NumberMantissaScaleGuard const sg(scale); - testZero(); - testLimits(); - testToString(); - testAdd(); - testSub(); - testMul(); - testDiv(); - testRoot(); - testRoot2(); - testPower1(); - testPower2(); - testConversions(); - testToInteger(); - testSquelch(); - testRelationals(); - testStream(); - testIncDec(); - testToStAmount(); - testTruncate(); - testRounding(); - testInt64(); + NumberMantissaScaleGuard const mg{mantissaScale}; + NumberRoundModeGuard const rg{Number::RoundingMode::ToNearest}; - testUpwardRoundsDown(); + auto const scale = Number::getMantissaScale(); + + // Case is + using Case = std::pair; + + auto const c = std::to_array({ + {Number{5'175'909'259'972'499'745LL, 22}, -1'074'951'375'311'646'003}, + {Number{1}, -1'074'956'551'220'905'975}, + {Number{1, 10}, -1'074'956'551'220'905'975}, + {Number{1, 20}, -1'074'956'551'220'905'975}, + {Number{1, 27}, -1'074'956'551'220'905'975}, + {Number{1, 28}, -1'074'956'551'220'905'974}, + {Number{1, 31}, -1'074'956'551'220'904'975}, + }); + + for (auto const& [y, expectedQ] : c) + { + Number const x{-1'074'956'551'220'905'975LL, 28}; + Number const res = x + y; + + BigInt const exact = toBigInt(x) + toBigInt(y); + BigInt const vres = toBigInt(res); + + BigInt ulp = 1; + for (int i = 0; i < res.exponent(); ++i) + ulp *= 10; + + BigInt const q = (exact - ulp / 2) / ulp; + Number const normalizedExact{static_cast(q), res.exponent()}; + BigInt const norm = toBigInt(normalizedExact); + + auto message = [&](auto const& comp) { + std::ostringstream log; + log << fmt(q) + " != " + fmt(comp) << "\n" + << " x = " << x << "\n y = " << y + << "\n exact = " << fmt(exact) + << "\n result (x + y) = " << fmt(vres) + << "\n normalize(exact) = " << fmt(norm) << "\n\n"; + return log.str(); + }; + + if (scale == MantissaRange::MantissaScale::Small) + { + auto const comp = toBigInt(Number{expectedQ, -3}); + EXPECT_EQ(q, comp) << message(comp); + } + else + { + EXPECT_EQ(q, expectedQ) << message(BigInt(expectedQ)); + } + EXPECT_EQ(normalizedExact, res); } } -}; - -BEAST_DEFINE_TESTSUITE(Number, basics, xrpl); +} } // namespace xrpl diff --git a/src/tests/libxrpl/basics/StringUtilities.cpp b/src/tests/libxrpl/basics/StringUtilities.cpp new file mode 100644 index 0000000000..a10711abdb --- /dev/null +++ b/src/tests/libxrpl/basics/StringUtilities.cpp @@ -0,0 +1,293 @@ +#include + +#include +#include + +#include + +#include + +namespace xrpl { + +class StringUtilitiesTest : public ::testing::Test +{ +public: + static void + testUnHexSuccess(std::string const& strIn, std::string const& strExpected) + { + auto rv = strUnHex(strIn); + EXPECT_TRUE(rv); + + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_EQ(makeSlice(*rv), makeSlice(strExpected)); + } + + static void + testUnHexFailure(std::string const& strIn) + { + auto rv = strUnHex(strIn); + EXPECT_FALSE(rv); + } +}; + +TEST_F(StringUtilitiesTest, un_hex) +{ + testUnHexSuccess("526970706c6544", "RippleD"); + testUnHexSuccess("A", "\n"); + testUnHexSuccess("0A", "\n"); + testUnHexSuccess("D0A", "\r\n"); + testUnHexSuccess("0D0A", "\r\n"); + testUnHexSuccess("200D0A", " \r\n"); + testUnHexSuccess("282A2B2C2D2E2F29", "(*+,-./)"); + + // Check for things which contain some or only invalid characters + testUnHexFailure("123X"); + testUnHexFailure("V"); + testUnHexFailure("XRP"); +} + +TEST_F(StringUtilitiesTest, parse_url) +{ + // Expected passes. + { + ParsedUrl pUrl; + EXPECT_TRUE(parseUrl(pUrl, "scheme://")); + EXPECT_EQ(pUrl.scheme, "scheme"); + EXPECT_TRUE(pUrl.username.empty()); + EXPECT_TRUE(pUrl.password.empty()); + EXPECT_TRUE(pUrl.domain.empty()); + EXPECT_FALSE(pUrl.port); + // RFC 3986: + // > In general, a URI that uses the generic syntax for authority + // with an empty path should be normalized to a path of "/". + // Do we want to normalize paths? + EXPECT_TRUE(pUrl.path.empty()); + } + + { + ParsedUrl pUrl; + EXPECT_TRUE(parseUrl(pUrl, "scheme:///")); + EXPECT_EQ(pUrl.scheme, "scheme"); + EXPECT_TRUE(pUrl.username.empty()); + EXPECT_TRUE(pUrl.password.empty()); + EXPECT_TRUE(pUrl.domain.empty()); + EXPECT_FALSE(pUrl.port); + EXPECT_EQ(pUrl.path, "/"); + } + + { + ParsedUrl pUrl; + EXPECT_TRUE(parseUrl(pUrl, "lower://domain")); + EXPECT_EQ(pUrl.scheme, "lower"); + EXPECT_TRUE(pUrl.username.empty()); + EXPECT_TRUE(pUrl.password.empty()); + EXPECT_EQ(pUrl.domain, "domain"); + EXPECT_FALSE(pUrl.port); + EXPECT_TRUE(pUrl.path.empty()); + } + + { + ParsedUrl pUrl; + EXPECT_TRUE(parseUrl(pUrl, "UPPER://domain:234/")); + EXPECT_EQ(pUrl.scheme, "upper"); + EXPECT_TRUE(pUrl.username.empty()); + EXPECT_TRUE(pUrl.password.empty()); + EXPECT_EQ(pUrl.domain, "domain"); + EXPECT_EQ(*pUrl.port, 234); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_EQ(pUrl.path, "/"); + } + + { + ParsedUrl pUrl; + EXPECT_TRUE(parseUrl(pUrl, "Mixed://domain/path")); + EXPECT_EQ(pUrl.scheme, "mixed"); + EXPECT_TRUE(pUrl.username.empty()); + EXPECT_TRUE(pUrl.password.empty()); + EXPECT_EQ(pUrl.domain, "domain"); + EXPECT_FALSE(pUrl.port); + EXPECT_EQ(pUrl.path, "/path"); + } + + { + ParsedUrl pUrl; + EXPECT_TRUE(parseUrl(pUrl, "scheme://[::1]:123/path")); + EXPECT_EQ(pUrl.scheme, "scheme"); + EXPECT_TRUE(pUrl.username.empty()); + EXPECT_TRUE(pUrl.password.empty()); + EXPECT_EQ(pUrl.domain, "::1"); + EXPECT_EQ(*pUrl.port, 123); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_EQ(pUrl.path, "/path"); + } + + { + ParsedUrl pUrl; + EXPECT_TRUE(parseUrl(pUrl, "scheme://user:pass@domain:123/abc:321")); + EXPECT_EQ(pUrl.scheme, "scheme"); + EXPECT_EQ(pUrl.username, "user"); + EXPECT_EQ(pUrl.password, "pass"); + EXPECT_EQ(pUrl.domain, "domain"); + EXPECT_EQ(*pUrl.port, 123); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_EQ(pUrl.path, "/abc:321"); + } + + { + ParsedUrl pUrl; + EXPECT_TRUE(parseUrl(pUrl, "scheme://user@domain:123/abc:321")); + EXPECT_EQ(pUrl.scheme, "scheme"); + EXPECT_EQ(pUrl.username, "user"); + EXPECT_TRUE(pUrl.password.empty()); + EXPECT_EQ(pUrl.domain, "domain"); + EXPECT_EQ(*pUrl.port, 123); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_EQ(pUrl.path, "/abc:321"); + } + + { + ParsedUrl pUrl; + EXPECT_TRUE(parseUrl(pUrl, "scheme://:pass@domain:123/abc:321")); + EXPECT_EQ(pUrl.scheme, "scheme"); + EXPECT_TRUE(pUrl.username.empty()); + EXPECT_EQ(pUrl.password, "pass"); + EXPECT_EQ(pUrl.domain, "domain"); + EXPECT_EQ(*pUrl.port, 123); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_EQ(pUrl.path, "/abc:321"); + } + + { + ParsedUrl pUrl; + EXPECT_TRUE(parseUrl(pUrl, "scheme://domain:123/abc:321")); + EXPECT_EQ(pUrl.scheme, "scheme"); + EXPECT_TRUE(pUrl.username.empty()); + EXPECT_TRUE(pUrl.password.empty()); + EXPECT_EQ(pUrl.domain, "domain"); + EXPECT_EQ(*pUrl.port, 123); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_EQ(pUrl.path, "/abc:321"); + } + + { + ParsedUrl pUrl; + EXPECT_TRUE(parseUrl(pUrl, "scheme://user:pass@domain/abc:321")); + EXPECT_EQ(pUrl.scheme, "scheme"); + EXPECT_EQ(pUrl.username, "user"); + EXPECT_EQ(pUrl.password, "pass"); + EXPECT_EQ(pUrl.domain, "domain"); + EXPECT_FALSE(pUrl.port); + EXPECT_EQ(pUrl.path, "/abc:321"); + } + + { + ParsedUrl pUrl; + EXPECT_TRUE(parseUrl(pUrl, "scheme://user@domain/abc:321")); + EXPECT_EQ(pUrl.scheme, "scheme"); + EXPECT_EQ(pUrl.username, "user"); + EXPECT_TRUE(pUrl.password.empty()); + EXPECT_EQ(pUrl.domain, "domain"); + EXPECT_FALSE(pUrl.port); + EXPECT_EQ(pUrl.path, "/abc:321"); + } + + { + ParsedUrl pUrl; + EXPECT_TRUE(parseUrl(pUrl, "scheme://:pass@domain/abc:321")); + EXPECT_EQ(pUrl.scheme, "scheme"); + EXPECT_TRUE(pUrl.username.empty()); + EXPECT_EQ(pUrl.password, "pass"); + EXPECT_EQ(pUrl.domain, "domain"); + EXPECT_FALSE(pUrl.port); + EXPECT_EQ(pUrl.path, "/abc:321"); + } + + { + ParsedUrl pUrl; + EXPECT_TRUE(parseUrl(pUrl, "scheme://domain/abc:321")); + EXPECT_EQ(pUrl.scheme, "scheme"); + EXPECT_TRUE(pUrl.username.empty()); + EXPECT_TRUE(pUrl.password.empty()); + EXPECT_EQ(pUrl.domain, "domain"); + EXPECT_FALSE(pUrl.port); + EXPECT_EQ(pUrl.path, "/abc:321"); + } + + { + ParsedUrl pUrl; + EXPECT_TRUE(parseUrl(pUrl, "scheme:///path/to/file")); + EXPECT_EQ(pUrl.scheme, "scheme"); + EXPECT_TRUE(pUrl.username.empty()); + EXPECT_TRUE(pUrl.password.empty()); + EXPECT_TRUE(pUrl.domain.empty()); + EXPECT_FALSE(pUrl.port); + EXPECT_EQ(pUrl.path, "/path/to/file"); + } + + { + ParsedUrl pUrl; + EXPECT_TRUE(parseUrl(pUrl, "scheme://user:pass@domain/path/with/an@sign")); + EXPECT_EQ(pUrl.scheme, "scheme"); + EXPECT_EQ(pUrl.username, "user"); + EXPECT_EQ(pUrl.password, "pass"); + EXPECT_EQ(pUrl.domain, "domain"); + EXPECT_FALSE(pUrl.port); + EXPECT_EQ(pUrl.path, "/path/with/an@sign"); + } + + { + ParsedUrl pUrl; + EXPECT_TRUE(parseUrl(pUrl, "scheme://domain/path/with/an@sign")); + EXPECT_EQ(pUrl.scheme, "scheme"); + EXPECT_TRUE(pUrl.username.empty()); + EXPECT_TRUE(pUrl.password.empty()); + EXPECT_EQ(pUrl.domain, "domain"); + EXPECT_FALSE(pUrl.port); + EXPECT_EQ(pUrl.path, "/path/with/an@sign"); + } + + { + ParsedUrl pUrl; + EXPECT_TRUE(parseUrl(pUrl, "scheme://:999/")); + EXPECT_EQ(pUrl.scheme, "scheme"); + EXPECT_TRUE(pUrl.username.empty()); + EXPECT_TRUE(pUrl.password.empty()); + EXPECT_EQ(pUrl.domain, ":999"); + EXPECT_FALSE(pUrl.port); + EXPECT_EQ(pUrl.path, "/"); + } + + { + ParsedUrl pUrl; + EXPECT_TRUE(parseUrl(pUrl, "http://::1:1234/validators")); + EXPECT_EQ(pUrl.scheme, "http"); + EXPECT_TRUE(pUrl.username.empty()); + EXPECT_TRUE(pUrl.password.empty()); + EXPECT_EQ(pUrl.domain, "::0.1.18.52"); + EXPECT_FALSE(pUrl.port); + EXPECT_EQ(pUrl.path, "/validators"); + } + + // Expected fails. + { + ParsedUrl pUrl; + EXPECT_FALSE(parseUrl(pUrl, "")); + EXPECT_FALSE(parseUrl(pUrl, "nonsense")); + EXPECT_FALSE(parseUrl(pUrl, "://")); + EXPECT_FALSE(parseUrl(pUrl, ":///")); + EXPECT_FALSE(parseUrl(pUrl, "scheme://user:pass@domain:65536/abc:321")); + EXPECT_FALSE(parseUrl(pUrl, "UPPER://domain:23498765/")); + EXPECT_FALSE(parseUrl(pUrl, "UPPER://domain:0/")); + EXPECT_FALSE(parseUrl(pUrl, "UPPER://domain:+7/")); + EXPECT_FALSE(parseUrl(pUrl, "UPPER://domain:-7234/")); + EXPECT_FALSE(parseUrl(pUrl, "UPPER://domain:@#$56!/")); + } + + { + std::string const strUrl("s://" + std::string(8192, ':')); + ParsedUrl pUrl; + EXPECT_FALSE(parseUrl(pUrl, strUrl)); + } +} + +TEST_F(StringUtilitiesTest, to_string) +{ + auto result = to_string("hello"); + EXPECT_EQ(result, "hello"); +} + +} // namespace xrpl diff --git a/src/tests/libxrpl/basics/TaggedCache.cpp b/src/tests/libxrpl/basics/TaggedCache.cpp new file mode 100644 index 0000000000..c8ccc415ad --- /dev/null +++ b/src/tests/libxrpl/basics/TaggedCache.cpp @@ -0,0 +1,246 @@ +#include + +#include +#include +#include // IWYU pragma: keep +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace xrpl { + +/* +I guess you can put some items in, make sure they're still there. Let some +time pass, make sure they're gone. Keep a strong pointer to one of them, make +sure you can still find it even after time passes. Create two objects with +the same key, canonicalize them both and make sure you get the same object. +Put an object in but keep a strong pointer to it, advance the clock a lot, +then canonicalize a new object with the same key, make sure you get the +original object. +*/ + +TEST(TaggedCacheTest, tagged_cache) +{ + using namespace std::chrono_literals; + beast::Journal const journal{TestSink::instance()}; + + TestStopwatch clock; + clock.set(0); + + using Key = LedgerIndex; + using Value = std::string; + using Cache = TaggedCache; + + Cache c("test", 1, 1s, clock, journal); + + // Insert an item, retrieve it, and age it so it gets purged. + { + EXPECT_EQ(c.getCacheSize(), 0); + EXPECT_EQ(c.getTrackSize(), 0); + EXPECT_FALSE(c.insert(1, "one")); + EXPECT_EQ(c.getCacheSize(), 1); + EXPECT_EQ(c.getTrackSize(), 1); + + { + std::string s; + EXPECT_TRUE(c.retrieve(1, s)); + EXPECT_EQ(s, "one"); + } + + ++clock; + c.sweep(); + EXPECT_EQ(c.getCacheSize(), 0); + EXPECT_EQ(c.getTrackSize(), 0); + } + + // Insert an item, maintain a strong pointer, age it, and + // verify that the entry still exists. + { + EXPECT_FALSE(c.insert(2, "two")); + EXPECT_EQ(c.getCacheSize(), 1); + EXPECT_EQ(c.getTrackSize(), 1); + + { + auto p = c.fetch(2); + EXPECT_NE(p, nullptr); + ++clock; + c.sweep(); + EXPECT_EQ(c.getCacheSize(), 0); + EXPECT_EQ(c.getTrackSize(), 1); + } + + // Make sure its gone now that our reference is gone + ++clock; + c.sweep(); + EXPECT_EQ(c.getCacheSize(), 0); + EXPECT_EQ(c.getTrackSize(), 0); + } + + // Insert the same key/value pair and make sure we get the same result + { + EXPECT_FALSE(c.insert(3, "three")); + + { + auto const p1 = c.fetch(3); + auto p2 = std::make_shared("three"); + c.canonicalizeReplaceClient(3, p2); + EXPECT_EQ(p1.get(), p2.get()); + } + ++clock; + c.sweep(); + EXPECT_EQ(c.getCacheSize(), 0); + EXPECT_EQ(c.getTrackSize(), 0); + } + + // Put an object in but keep a strong pointer to it, advance the clock a + // lot, then canonicalize a new object with the same key, make sure you + // get the original object. + { + // Put an object in + EXPECT_FALSE(c.insert(4, "four")); + EXPECT_EQ(c.getCacheSize(), 1); + EXPECT_EQ(c.getTrackSize(), 1); + + { + // Keep a strong pointer to it + auto const p1 = c.fetch(4); + EXPECT_NE(p1, nullptr); + EXPECT_EQ(c.getCacheSize(), 1); + EXPECT_EQ(c.getTrackSize(), 1); + // Advance the clock a lot + ++clock; + c.sweep(); + EXPECT_EQ(c.getCacheSize(), 0); + EXPECT_EQ(c.getTrackSize(), 1); + // Canonicalize a new object with the same key + auto p2 = std::make_shared("four"); + EXPECT_TRUE(c.canonicalizeReplaceClient(4, p2)); + EXPECT_EQ(c.getCacheSize(), 1); + EXPECT_EQ(c.getTrackSize(), 1); + // Make sure we get the original object + EXPECT_EQ(p1.get(), p2.get()); + } + + ++clock; + c.sweep(); + EXPECT_EQ(c.getCacheSize(), 0); + EXPECT_EQ(c.getTrackSize(), 0); + } + + { + EXPECT_FALSE(c.insert(5, "five")); + EXPECT_EQ(c.getCacheSize(), 1); + EXPECT_EQ(c.size(), 1); + + { + auto const p1 = c.fetch(5); + EXPECT_NE(p1, nullptr); + EXPECT_EQ(c.getCacheSize(), 1); + EXPECT_EQ(c.size(), 1); + + // Advance the clock a lot + ++clock; + c.sweep(); + EXPECT_EQ(c.getCacheSize(), 0); + EXPECT_EQ(c.size(), 1); + + auto p2 = std::make_shared("five_2"); + EXPECT_TRUE(c.canonicalizeReplaceCache(5, p2)); + EXPECT_EQ(c.getCacheSize(), 1); + EXPECT_EQ(c.size(), 1); + // Make sure the caller's original pointer is unchanged + EXPECT_NE(p1.get(), p2.get()); + EXPECT_EQ(*p2, "five_2"); + + auto const p3 = c.fetch(5); + EXPECT_NE(p3, nullptr); + EXPECT_EQ(p3.get(), p2.get()); + EXPECT_NE(p3.get(), p1.get()); + } + + ++clock; + c.sweep(); + EXPECT_EQ(c.getCacheSize(), 0); + EXPECT_EQ(c.size(), 0); + } + + { + struct MyRefCountObject : IntrusiveRefCounts + { + std::string data; + + // Needed to support weak intrusive pointers + virtual void + partialDestructor() + { + } + + MyRefCountObject() = default; + explicit MyRefCountObject(std::string data) : data(std::move(data)) + { + } + + bool + operator==(std::string const& other) const + { + return data == other; + } + }; + + using IntrPtrCache = TaggedCache< + Key, + MyRefCountObject, + /*IsKeyCache*/ false, + intr_ptr::SharedWeakUnionPtr, + intr_ptr::SharedPtr>; + + IntrPtrCache intrPtrCache("IntrPtrTest", 1, 1s, clock, journal); + + intrPtrCache.canonicalizeReplaceCache(1, intr_ptr::makeShared("one")); + EXPECT_EQ(intrPtrCache.getCacheSize(), 1); + EXPECT_EQ(intrPtrCache.size(), 1); + + { + { + intrPtrCache.canonicalizeReplaceCache( + 1, intr_ptr::makeShared("one_replaced")); + + auto p = intrPtrCache.fetch(1); + EXPECT_EQ(*p, "one_replaced"); + + // Advance the clock a lot + ++clock; + intrPtrCache.sweep(); + EXPECT_EQ(intrPtrCache.getCacheSize(), 0); + EXPECT_EQ(intrPtrCache.size(), 1); + + intrPtrCache.canonicalizeReplaceCache( + 1, intr_ptr::makeShared("one_replaced_2")); + + auto p2 = intrPtrCache.fetch(1); + EXPECT_EQ(*p2, "one_replaced_2"); + + intrPtrCache.del(1, true); + } + + intrPtrCache.canonicalizeReplaceCache( + 1, intr_ptr::makeShared("one_replaced_3")); + auto p3 = intrPtrCache.fetch(1); + EXPECT_EQ(*p3, "one_replaced_3"); + } + + ++clock; + intrPtrCache.sweep(); + EXPECT_EQ(intrPtrCache.getCacheSize(), 0); + EXPECT_EQ(intrPtrCache.size(), 0); + } +} + +} // namespace xrpl diff --git a/src/tests/libxrpl/basics/Units.cpp b/src/tests/libxrpl/basics/Units.cpp new file mode 100644 index 0000000000..1cf7024fe0 --- /dev/null +++ b/src/tests/libxrpl/basics/Units.cpp @@ -0,0 +1,328 @@ +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace xrpl::test { + +TEST(UnitsTest, types) +{ + using FeeLevel32 = FeeLevel; + + { + XRPAmount const x{100}; + EXPECT_EQ(x.drops(), 100); + EXPECT_TRUE((std::is_same_v)); + auto y = 4u * x; + EXPECT_EQ(y.value(), 400); + EXPECT_TRUE((std::is_same_v)); + + auto z = 4 * y; + EXPECT_EQ(z.value(), 1600); + EXPECT_TRUE((std::is_same_v)); + + FeeLevel32 const f{10}; + FeeLevel32 const baseFee{100}; + + auto drops = mulDiv(baseFee, x, f); + + EXPECT_TRUE(drops); + EXPECT_EQ(drops.value(), 1000); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_TRUE( + (std::is_same_v::unit_type, unit::dropTag>)); + + EXPECT_TRUE((std::is_same_v, XRPAmount>)); + } + { + XRPAmount const x{100}; + EXPECT_EQ(x.value(), 100); + EXPECT_TRUE((std::is_same_v)); + auto y = 4u * x; + EXPECT_EQ(y.value(), 400); + EXPECT_TRUE((std::is_same_v)); + + FeeLevel64 const f{10}; + FeeLevel64 const baseFee{100}; + + auto drops = mulDiv(baseFee, x, f); + + EXPECT_TRUE(drops); + EXPECT_EQ(drops.value(), 1000); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_TRUE( + (std::is_same_v::unit_type, unit::dropTag>)); + EXPECT_TRUE((std::is_same_v, XRPAmount>)); + } + { + FeeLevel64 const x{1024}; + EXPECT_EQ(x.value(), 1024); + EXPECT_TRUE((std::is_same_v)); + std::uint64_t const m = 4; + auto y = m * x; + EXPECT_EQ(y.value(), 4096); + EXPECT_TRUE((std::is_same_v)); + + XRPAmount const basefee{10}; + FeeLevel64 const referencefee{256}; + + auto drops = mulDiv(x, basefee, referencefee); + + EXPECT_TRUE(drops); + EXPECT_EQ(drops.value(), 40); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_TRUE( + (std::is_same_v::unit_type, unit::dropTag>)); + EXPECT_TRUE((std::is_same_v, XRPAmount>)); + } +} + +TEST(UnitsTest, json) +{ + // Json value functionality + using FeeLevel32 = FeeLevel; + + { + FeeLevel32 const x{std::numeric_limits::max()}; + auto y = x.jsonClipped(); + EXPECT_EQ(y.type(), json::ValueType::UInt); + EXPECT_EQ(y, json::Value{x.fee()}); + } + + { + FeeLevel32 const x{std::numeric_limits::min()}; + auto y = x.jsonClipped(); + EXPECT_EQ(y.type(), json::ValueType::UInt); + EXPECT_EQ(y, json::Value{x.fee()}); + } + + { + FeeLevel64 const x{std::numeric_limits::max()}; + auto y = x.jsonClipped(); + EXPECT_EQ(y.type(), json::ValueType::UInt); + EXPECT_EQ(y, json::Value{std::numeric_limits::max()}); + } + + { + FeeLevel64 const x{std::numeric_limits::min()}; + auto y = x.jsonClipped(); + EXPECT_EQ(y.type(), json::ValueType::UInt); + EXPECT_EQ(y, json::Value{0}); + } + + { + FeeLevelDouble const x{std::numeric_limits::max()}; + auto y = x.jsonClipped(); + EXPECT_EQ(y.type(), json::ValueType::Real); + EXPECT_EQ(y, json::Value{std::numeric_limits::max()}); + } + + { + FeeLevelDouble const x{std::numeric_limits::min()}; + auto y = x.jsonClipped(); + EXPECT_EQ(y.type(), json::ValueType::Real); + EXPECT_EQ(y, json::Value{std::numeric_limits::min()}); + } + + { + XRPAmount const x{std::numeric_limits::max()}; + auto y = x.jsonClipped(); + EXPECT_EQ(y.type(), json::ValueType::Int); + EXPECT_EQ(y, json::Value{std::numeric_limits::max()}); + } + + { + XRPAmount const x{std::numeric_limits::min()}; + auto y = x.jsonClipped(); + EXPECT_EQ(y.type(), json::ValueType::Int); + EXPECT_EQ(y, json::Value{std::numeric_limits::min()}); + } +} + +TEST(UnitsTest, functions) +{ + // Explicitly test every defined function for the ValueUnit class + // since some of them are templated, but not used anywhere else. + using FeeLevel32 = FeeLevel; + + { + auto make = [&](auto x) -> FeeLevel64 { return x; }; + auto explicitmake = [&](auto x) -> FeeLevel64 { return FeeLevel64{x}; }; + + [[maybe_unused]] + FeeLevel64 const defaulted{}; + FeeLevel64 test{0}; + EXPECT_EQ(test.fee(), 0); + + test = explicitmake(beast::kZero); + EXPECT_EQ(test.fee(), 0); + + test = beast::kZero; + EXPECT_EQ(test.fee(), 0); + + test = explicitmake(100u); + EXPECT_EQ(test.fee(), 100); + + FeeLevel64 const targetSame{200u}; + FeeLevel32 const targetOther{300u}; + test = make(targetSame); + EXPECT_EQ(test.fee(), 200); + EXPECT_EQ(test, targetSame); + EXPECT_TRUE(test < FeeLevel64{1000}); + EXPECT_TRUE(test > FeeLevel64{100}); + test = make(targetOther); + EXPECT_EQ(test.fee(), 300); + EXPECT_EQ(test, targetOther); + + test = std::uint64_t(200); + EXPECT_EQ(test.fee(), 200); + test = std::uint32_t(300); + EXPECT_EQ(test.fee(), 300); + + test = targetSame; + EXPECT_EQ(test.fee(), 200); + test = targetOther.fee(); + EXPECT_EQ(test.fee(), 300); + EXPECT_EQ(test, targetOther); + + test = targetSame * 2; + EXPECT_EQ(test.fee(), 400); + test = 3 * targetSame; + EXPECT_EQ(test.fee(), 600); + test = targetSame / 10; + EXPECT_EQ(test.fee(), 20); + + test += targetSame; + EXPECT_EQ(test.fee(), 220); + + test -= targetSame; + EXPECT_EQ(test.fee(), 20); + + test++; + EXPECT_EQ(test.fee(), 21); + ++test; + EXPECT_EQ(test.fee(), 22); + test--; + EXPECT_EQ(test.fee(), 21); + --test; + EXPECT_EQ(test.fee(), 20); + + test *= 5; + EXPECT_EQ(test.fee(), 100); + test /= 2; + EXPECT_EQ(test.fee(), 50); + test %= 13; + EXPECT_EQ(test.fee(), 11); + + /* + // illegal with unsigned + test = -test; + EXPECT_EQ(test.fee(), -11); + EXPECT_EQ(test.signum(), -1); + EXPECT_EQ(to_string(test), "-11"); + */ + + EXPECT_TRUE(test); + test = 0; + EXPECT_FALSE(test); + EXPECT_EQ(test.signum(), 0); + test = targetSame; + EXPECT_EQ(test.signum(), 1); + EXPECT_EQ(to_string(test), "200"); + } + { + auto make = [&](auto x) -> FeeLevelDouble { return x; }; + auto explicitmake = [&](auto x) -> FeeLevelDouble { return FeeLevelDouble{x}; }; + + [[maybe_unused]] + FeeLevelDouble const defaulted{}; + FeeLevelDouble test{0}; + EXPECT_EQ(test.fee(), 0); + + test = explicitmake(beast::kZero); + EXPECT_EQ(test.fee(), 0); + + test = beast::kZero; + EXPECT_EQ(test.fee(), 0); + + test = explicitmake(100.0); + EXPECT_EQ(test.fee(), 100); + + FeeLevelDouble const targetSame{200.0}; + FeeLevel64 const targetOther{300}; + test = make(targetSame); + EXPECT_EQ(test.fee(), 200); + EXPECT_EQ(test, targetSame); + EXPECT_TRUE(test < FeeLevelDouble{1000.0}); + EXPECT_TRUE(test > FeeLevelDouble{100.0}); + test = targetOther.fee(); + EXPECT_EQ(test.fee(), 300); + EXPECT_EQ(test, targetOther); + + test = 200.0; + EXPECT_EQ(test.fee(), 200); + test = std::uint64_t(300); + EXPECT_EQ(test.fee(), 300); + + test = targetSame; + EXPECT_EQ(test.fee(), 200); + + test = targetSame * 2; + EXPECT_EQ(test.fee(), 400); + test = 3 * targetSame; + EXPECT_EQ(test.fee(), 600); + test = targetSame / 10; + EXPECT_EQ(test.fee(), 20); + + test += targetSame; + EXPECT_EQ(test.fee(), 220); + + test -= targetSame; + EXPECT_EQ(test.fee(), 20); + + test++; + EXPECT_EQ(test.fee(), 21); + ++test; + EXPECT_EQ(test.fee(), 22); + test--; + EXPECT_EQ(test.fee(), 21); + --test; + EXPECT_EQ(test.fee(), 20); + + test *= 5; + EXPECT_EQ(test.fee(), 100); + test /= 2; + EXPECT_EQ(test.fee(), 50); + /* illegal with floating + test %= 13; + EXPECT_EQ(test.fee(), 11); + */ + + // legal with signed + test = -test; + EXPECT_EQ(test.fee(), -50); + EXPECT_EQ(test.signum(), -1); + EXPECT_EQ(to_string(test), "-50.000000"); + + EXPECT_TRUE(test); + test = 0; + EXPECT_FALSE(test); + EXPECT_EQ(test.signum(), 0); + test = targetSame; + EXPECT_EQ(test.signum(), 1); + EXPECT_EQ(to_string(test), "200.000000"); + } +} + +TEST(UnitsTest, initial_xrp) +{ + EXPECT_EQ(kInitialXrp.drops(), 100'000'000'000'000'000); + EXPECT_EQ(kInitialXrp, XRPAmount{100'000'000'000'000'000}); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/basics/XRPAmount.cpp b/src/tests/libxrpl/basics/XRPAmount.cpp new file mode 100644 index 0000000000..243afbd1a0 --- /dev/null +++ b/src/tests/libxrpl/basics/XRPAmount.cpp @@ -0,0 +1,295 @@ +#include + +#include + +#include + +#include +#include + +namespace xrpl { + +TEST(XRPAmountTest, sig_num) +{ + for (auto i : {-1, 0, 1}) + { + XRPAmount const x(i); + + if (i < 0) + { + EXPECT_TRUE(x.signum() < 0); + } + else if (i > 0) + { + EXPECT_TRUE(x.signum() > 0); + } + else + { + EXPECT_EQ(x.signum(), 0); + } + } +} + +TEST(XRPAmountTest, beast_zero) +{ + using beast::kZero; + + for (auto i : {-1, 0, 1}) + { + XRPAmount const x(i); + + EXPECT_TRUE((i == 0) == (x == kZero)); + EXPECT_TRUE((i != 0) == (x != kZero)); + EXPECT_TRUE((i < 0) == (x < kZero)); + EXPECT_TRUE((i > 0) == (x > kZero)); + EXPECT_TRUE((i <= 0) == (x <= kZero)); + EXPECT_TRUE((i >= 0) == (x >= kZero)); + + EXPECT_TRUE((0 == i) == (kZero == x)); + EXPECT_TRUE((0 != i) == (kZero != x)); + EXPECT_TRUE((0 < i) == (kZero < x)); + EXPECT_TRUE((0 > i) == (kZero > x)); + EXPECT_TRUE((0 <= i) == (kZero <= x)); + EXPECT_TRUE((0 >= i) == (kZero >= x)); + } +} + +TEST(XRPAmountTest, comparisons) +{ + for (auto i : {-1, 0, 1}) + { + XRPAmount const x(i); + + for (auto j : {-1, 0, 1}) + { + XRPAmount const y(j); + + EXPECT_EQ((i == j), (x == y)); + EXPECT_EQ((i != j), (x != y)); + EXPECT_EQ((i < j), (x < y)); + EXPECT_EQ((i > j), (x > y)); + EXPECT_EQ((i <= j), (x <= y)); + EXPECT_EQ((i >= j), (x >= y)); + } + } +} + +TEST(XRPAmountTest, add_sub) +{ + for (auto i : {-1, 0, 1}) + { + XRPAmount const x(i); + + for (auto j : {-1, 0, 1}) + { + XRPAmount const y(j); + + EXPECT_EQ(XRPAmount(i + j), (x + y)); + EXPECT_EQ(XRPAmount(i - j), (x - y)); + + EXPECT_EQ((x + y), (y + x)); // addition is commutative + } + } +} + +TEST(XRPAmountTest, decimal) +{ + // Tautology + EXPECT_EQ(kDropsPerXrp.decimalXRP(), 1); + + XRPAmount test{1}; + EXPECT_EQ(test.decimalXRP(), 0.000001); + + test = -test; + EXPECT_EQ(test.decimalXRP(), -0.000001); + + test = 100'000'000; + EXPECT_EQ(test.decimalXRP(), 100); + + test = -test; + EXPECT_EQ(test.decimalXRP(), -100); +} + +TEST(XRPAmountTest, functions) +{ + // Explicitly test every defined function for the XRPAmount class + // since some of them are templated, but not used anywhere else. + auto make = [&](auto x) -> XRPAmount { return XRPAmount{x}; }; + + XRPAmount const defaulted{}; + (void)defaulted; + XRPAmount test{0}; + EXPECT_EQ(test.drops(), 0); + + test = make(beast::kZero); + EXPECT_EQ(test.drops(), 0); + + test = beast::kZero; + EXPECT_EQ(test.drops(), 0); + + test = make(100); + EXPECT_EQ(test.drops(), 100); + + test = make(100u); + EXPECT_EQ(test.drops(), 100); + + XRPAmount const targetSame{200u}; + test = make(targetSame); + EXPECT_EQ(test.drops(), 200); + EXPECT_EQ(test, targetSame); + EXPECT_TRUE(test < XRPAmount{1000}); + EXPECT_TRUE(test > XRPAmount{100}); + + test = std::int64_t(200); + EXPECT_EQ(test.drops(), 200); + test = std::uint32_t(300); + EXPECT_EQ(test.drops(), 300); + + test = targetSame; + EXPECT_EQ(test.drops(), 200); + auto testOther = test.dropsAs(); + EXPECT_TRUE(testOther); + EXPECT_EQ(*testOther, 200); // NOLINT(bugprone-unchecked-optional-access) + test = std::numeric_limits::max(); + testOther = test.dropsAs(); + EXPECT_FALSE(testOther); + test = -1; + testOther = test.dropsAs(); + EXPECT_FALSE(testOther); + + test = targetSame * 2; + EXPECT_EQ(test.drops(), 400); + test = 3 * targetSame; + EXPECT_EQ(test.drops(), 600); + test = 20; + EXPECT_EQ(test.drops(), 20); + + test += targetSame; + EXPECT_EQ(test.drops(), 220); + + test -= targetSame; + EXPECT_EQ(test.drops(), 20); + + test *= 5; + EXPECT_EQ(test.drops(), 100); + test = 50; + EXPECT_EQ(test.drops(), 50); + test -= 39; + EXPECT_EQ(test.drops(), 11); + + // legal with signed + test = -test; + EXPECT_EQ(test.drops(), -11); + EXPECT_EQ(test.signum(), -1); + EXPECT_EQ(to_string(test), "-11"); + + EXPECT_TRUE(test); + test = 0; + EXPECT_FALSE(test); + EXPECT_EQ(test.signum(), 0); + test = targetSame; + EXPECT_EQ(test.signum(), 1); + EXPECT_EQ(to_string(test), "200"); +} + +TEST(XRPAmountTest, mul_ratio) +{ + constexpr auto kMaxUInt32 = std::numeric_limits::max(); + constexpr auto kMaxXrp = std::numeric_limits::max(); + constexpr auto kMinXrp = std::numeric_limits::min(); + + { + // multiply by a number that would overflow then divide by the same + // number, and check we didn't lose any value + XRPAmount big(kMaxXrp); + EXPECT_EQ(big, mulRatio(big, kMaxUInt32, kMaxUInt32, true)); + // rounding mode shouldn't matter as the result is exact + EXPECT_EQ(big, mulRatio(big, kMaxUInt32, kMaxUInt32, false)); + + // multiply and divide by values that would overflow if done + // naively, and check that it gives the correct answer + big -= 0xf; // Subtract a little so it's divisible by 4 + EXPECT_EQ(mulRatio(big, 3, 4, false).value(), (big.value() / 4) * 3); + EXPECT_EQ(mulRatio(big, 3, 4, true).value(), (big.value() / 4) * 3); + EXPECT_EQ(big.value() % 4, 0); + EXPECT_GT(big.value(), kMaxXrp / 3); + EXPECT_LE(big.value() / 4, kMaxXrp / 3); + } + + { + // Similar test as above, but for negative values + XRPAmount big(kMinXrp); // NOLINT TODO + EXPECT_EQ(big, mulRatio(big, kMaxUInt32, kMaxUInt32, true)); + // rounding mode shouldn't matter as the result is exact + EXPECT_EQ(big, mulRatio(big, kMaxUInt32, kMaxUInt32, false)); + + // multiply and divide by values that would overflow if done + // naively, and check that it gives the correct answer + EXPECT_EQ(mulRatio(big, 3, 4, false).value(), (big.value() / 4) * 3); + EXPECT_EQ(mulRatio(big, 3, 4, true).value(), (big.value() / 4) * 3); + EXPECT_EQ(big.value() % 4, 0); + EXPECT_LT(big.value(), kMinXrp / 3); + EXPECT_GE(big.value() / 4, kMinXrp / 3); + } + + { + // small amounts + XRPAmount const tiny(1); + // Round up should give the smallest allowable number + EXPECT_EQ(tiny, mulRatio(tiny, 1, kMaxUInt32, true)); + // rounding down should be zero + EXPECT_EQ(beast::kZero, mulRatio(tiny, 1, kMaxUInt32, false)); + EXPECT_EQ(beast::kZero, mulRatio(tiny, kMaxUInt32 - 1, kMaxUInt32, false)); + + // tiny negative numbers + XRPAmount const tinyNeg(-1); + // Round up should give zero + EXPECT_EQ(beast::kZero, mulRatio(tinyNeg, 1, kMaxUInt32, true)); + EXPECT_EQ(beast::kZero, mulRatio(tinyNeg, kMaxUInt32 - 1, kMaxUInt32, true)); + // rounding down should be tiny + EXPECT_EQ(tinyNeg, mulRatio(tinyNeg, kMaxUInt32 - 1, kMaxUInt32, false)); + } + + { // rounding + { + XRPAmount const one(1); + auto const rup = mulRatio(one, kMaxUInt32 - 1, kMaxUInt32, true); + auto const rdown = mulRatio(one, kMaxUInt32 - 1, kMaxUInt32, false); + EXPECT_EQ(rup.drops() - rdown.drops(), 1); + } + + { + XRPAmount const big(kMaxXrp); + auto const rup = mulRatio(big, kMaxUInt32 - 1, kMaxUInt32, true); + auto const rdown = mulRatio(big, kMaxUInt32 - 1, kMaxUInt32, false); + EXPECT_EQ(rup.drops() - rdown.drops(), 1); + } + + { + XRPAmount const negOne(-1); + auto const rup = mulRatio(negOne, kMaxUInt32 - 1, kMaxUInt32, true); + auto const rdown = mulRatio(negOne, kMaxUInt32 - 1, kMaxUInt32, false); + EXPECT_EQ(rup.drops() - rdown.drops(), 1); + } + } + + { + // division by zero + XRPAmount const one(1); + EXPECT_ANY_THROW({ mulRatio(one, 1, 0, true); }); + } + + { + // overflow + XRPAmount const big(kMaxXrp); + EXPECT_ANY_THROW({ mulRatio(big, 2, 1, true); }); + } + + { + // underflow + XRPAmount const bigNegative(kMinXrp + 10); + EXPECT_EQ(mulRatio(bigNegative, 2, 1, true), kMinXrp); + } +} + +} // namespace xrpl diff --git a/src/tests/libxrpl/basics/base58.cpp b/src/tests/libxrpl/basics/base58.cpp new file mode 100644 index 0000000000..d452453f76 --- /dev/null +++ b/src/tests/libxrpl/basics/base58.cpp @@ -0,0 +1,438 @@ +#include + +#include // IWYU pragma: keep + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef _MSC_VER + +#include +#include + +#include +#include +#include +#include +#include + +namespace xrpl::test { +namespace { + +[[nodiscard]] inline auto +randEngine() -> std::mt19937& +{ + static std::mt19937 kR = [] { + std::random_device rd; + return std::mt19937{rd()}; + }(); + return kR; +} + +constexpr int kNumTokenTypeIndexes = 9; + +[[nodiscard]] inline auto +tokenTypeAndSize(int i) -> std::tuple +{ + assert(i < kNumTokenTypeIndexes); + + switch (i) + { + using enum xrpl::TokenType; + case 0: + return {None, 20}; + case 1: + return {NodePublic, 32}; + case 2: + return {NodePublic, 33}; + case 3: + return {NodePrivate, 32}; + case 4: + return {AccountID, 20}; + case 5: + return {AccountPublic, 32}; + case 6: + return {AccountPublic, 33}; + case 7: + return {AccountSecret, 32}; + case 8: + return {FamilySeed, 16}; + default: + throw std::invalid_argument( + "Invalid token selection passed to tokenTypeAndSize() " + "in " __FILE__); + } +} + +[[nodiscard]] inline auto +randomTokenTypeAndSize() -> std::tuple +{ + using namespace xrpl; + auto& rng = randEngine(); + std::uniform_int_distribution<> d(0, 8); + return tokenTypeAndSize(d(rng)); +} + +// Return the token type and subspan of `d` to use as test data. +[[nodiscard]] inline auto +randomB256TestData(std::span d) + -> std::tuple> +{ + auto& rng = randEngine(); + std::uniform_int_distribution dist(0, 255); + auto [tokType, tokSize] = randomTokenTypeAndSize(); + std::generate(d.begin(), d.begin() + tokSize, [&] { return dist(rng); }); + return {tokType, d.subspan(0, tokSize)}; +} + +inline void +printAsChar(std::span a, std::span b) +{ + auto asString = [](std::span s) { + std::string r; + r.resize(s.size()); + std::ranges::copy(s, r.begin()); + return r; + }; + auto sa = asString(a); + auto sb = asString(b); + std::cerr << "\n\n" << sa << "\n" << sb << "\n"; +} + +inline void +printAsInt(std::span a, std::span b) +{ + auto asString = [](std::span s) -> std::string { + std::stringstream sstr; + for (auto i : s) + { + sstr << std::setw(3) << int(i) << ','; + } + return sstr.str(); + }; + auto sa = asString(a); + auto sb = asString(b); + std::cerr << "\n\n" << sa << "\n" << sb << "\n"; +} + +} // namespace + +namespace multiprecision_utils { + +boost::multiprecision::checked_uint512_t +toBoostMP(std::span in) +{ + boost::multiprecision::checked_uint512_t mbp = 0; + for (auto const& i : std::views::reverse(in)) + { + mbp <<= 64; + mbp += i; + } + return mbp; +} + +std::vector +randomBigInt(std::uint8_t minSize = 1, std::uint8_t maxSize = 5) +{ + auto eng = randEngine(); + std::uniform_int_distribution numCoeffDist(minSize, maxSize); + std::uniform_int_distribution dist; + auto const numCoeff = numCoeffDist(eng); + std::vector coeffs; + coeffs.reserve(numCoeff); + for (int i = 0; i < numCoeff; ++i) + { + coeffs.push_back(dist(eng)); + } + return coeffs; +} +} // namespace multiprecision_utils + +TEST(Base58Test, multiprecision) +{ + using namespace boost::multiprecision; + + constexpr std::size_t kIters = 100000; + auto eng = randEngine(); + std::uniform_int_distribution dist; + std::uniform_int_distribution dist1(1); + for (int i = 0; i < kIters; ++i) + { + std::uint64_t const d = dist(eng); + if (d == 0u) + continue; + auto bigInt = multiprecision_utils::randomBigInt(); + auto const boostBigInt = + multiprecision_utils::toBoostMP(std::span(bigInt.data(), bigInt.size())); + + auto const refDiv = boostBigInt / d; + auto const refMod = boostBigInt % d; + + auto const mod = b58_fast::detail::inplaceBigintDivRem( + std::span(bigInt.data(), bigInt.size()), d); + auto const foundDiv = multiprecision_utils::toBoostMP(bigInt); + EXPECT_EQ(refMod.convert_to(), mod); + EXPECT_EQ(foundDiv, refDiv); + } + for (int i = 0; i < kIters; ++i) + { + std::uint64_t const d = dist(eng); + auto bigInt = multiprecision_utils::randomBigInt(/*minSize*/ 2); + if (bigInt[bigInt.size() - 1] == std::numeric_limits::max()) + { + bigInt[bigInt.size() - 1] -= 1; // Prevent overflow + } + auto const boostBigInt = + multiprecision_utils::toBoostMP(std::span(bigInt.data(), bigInt.size())); + + auto const refAdd = boostBigInt + d; + + auto const result = b58_fast::detail::inplaceBigintAdd( + std::span(bigInt.data(), bigInt.size()), d); + EXPECT_EQ(result, TokenCodecErrc::Success); + auto const foundAdd = multiprecision_utils::toBoostMP(bigInt); + EXPECT_EQ(refAdd, foundAdd); + } + for (int i = 0; i < kIters; ++i) + { + std::uint64_t const d = dist1(eng); + // Force overflow + std::vector bigInt(5, std::numeric_limits::max()); + + auto const boostBigInt = + multiprecision_utils::toBoostMP(std::span(bigInt.data(), bigInt.size())); + + auto const refAdd = boostBigInt + d; + + auto const result = b58_fast::detail::inplaceBigintAdd( + std::span(bigInt.data(), bigInt.size()), d); + EXPECT_EQ(result, TokenCodecErrc::OverflowAdd); + auto const foundAdd = multiprecision_utils::toBoostMP(bigInt); + EXPECT_NE(refAdd, foundAdd); + } + for (int i = 0; i < kIters; ++i) + { + std::uint64_t const d = dist(eng); + auto bigInt = multiprecision_utils::randomBigInt(/* minSize */ 2); + // inplace mul requires the most significant coeff to be zero to + // hold the result. + bigInt[bigInt.size() - 1] = 0; + auto const boostBigInt = + multiprecision_utils::toBoostMP(std::span(bigInt.data(), bigInt.size())); + + auto const refMul = boostBigInt * d; + + auto const result = b58_fast::detail::inplaceBigintMul( + std::span(bigInt.data(), bigInt.size()), d); + EXPECT_EQ(result, TokenCodecErrc::Success); + auto const foundMul = multiprecision_utils::toBoostMP(bigInt); + EXPECT_EQ(refMul, foundMul); + } + for (int i = 0; i < kIters; ++i) + { + std::uint64_t const d = dist1(eng); + // Force overflow + std::vector bigInt(5, std::numeric_limits::max()); + auto const boostBigInt = + multiprecision_utils::toBoostMP(std::span(bigInt.data(), bigInt.size())); + + auto const refMul = boostBigInt * d; + + auto const result = b58_fast::detail::inplaceBigintMul( + std::span(bigInt.data(), bigInt.size()), d); + EXPECT_EQ(result, TokenCodecErrc::InputTooLarge); + auto const foundMul = multiprecision_utils::toBoostMP(bigInt); + EXPECT_NE(refMul, foundMul); + } +} + +TEST(Base58Test, fast_matches_ref) +{ + auto testRawEncode = [&](std::span const& b256Data) { + std::array b58ResultBuf[2]; + std::array, 2> b58Result; + + std::array b256ResultBuf[2]; + std::array, 2> b256Result; + for (int i = 0; i < 2; ++i) + { + std::span const outBuf{b58ResultBuf[i]}; + if (i == 0) + { + auto const r = xrpl::b58_fast::detail::b256ToB58Be(b256Data, outBuf); + EXPECT_TRUE(r); + b58Result[i] = r.value(); + } + else + { + std::array tmpBuf{}; + std::string const s = xrpl::b58_ref::detail::encodeBase58( + b256Data.data(), b256Data.size(), tmpBuf.data(), tmpBuf.size()); + EXPECT_TRUE(s.size()); + b58Result[i] = outBuf.subspan(0, s.size()); + std::ranges::copy(s, b58Result[i].begin()); + } + } + auto const rawB58SameSize = b58Result[0].size() == b58Result[1].size(); + EXPECT_TRUE(rawB58SameSize); + if (rawB58SameSize) + { + auto const rawB58SameData = + memcmp(b58Result[0].data(), b58Result[1].data(), b58Result[0].size()) == 0; + EXPECT_TRUE(rawB58SameData); + if (!rawB58SameData) + { + printAsChar(b58Result[0], b58Result[1]); + } + } + + for (int i = 0; i < 2; ++i) + { + std::span const outBuf{b256ResultBuf[i].data(), b256ResultBuf[i].size()}; + if (i == 0) + { + std::string const in( + b58Result[i].data(), b58Result[i].data() + b58Result[i].size()); + auto const r = xrpl::b58_fast::detail::b58ToB256Be(in, outBuf); + EXPECT_TRUE(r); + b256Result[i] = r.value(); + } + else + { + std::string const st(b58Result[i].begin(), b58Result[i].end()); + std::string const s = xrpl::b58_ref::detail::decodeBase58(st); + EXPECT_TRUE(s.size()); + b256Result[i] = outBuf.subspan(0, s.size()); + std::ranges::copy(s, b256Result[i].begin()); + } + } + + auto const rawB256SameSize = b256Result[0].size() == b256Result[1].size(); + EXPECT_TRUE(rawB256SameSize); + if (rawB256SameSize) + { + auto const rawB256SameData = + memcmp(b256Result[0].data(), b256Result[1].data(), b256Result[0].size()) == 0; + EXPECT_TRUE(rawB256SameData); + if (!rawB256SameData) + { + printAsInt(b256Result[0], b256Result[1]); + } + } + }; + + auto testTokenEncode = [&](xrpl::TokenType const tokType, + std::span const& b256Data) { + std::array b58ResultBuf[2]; + std::array, 2> b58Result; + + std::array b256ResultBuf[2]; + std::array, 2> b256Result; + for (int i = 0; i < 2; ++i) + { + std::span const outBuf{b58ResultBuf[i].data(), b58ResultBuf[i].size()}; + if (i == 0) + { + auto const r = xrpl::b58_fast::encodeBase58Token(tokType, b256Data, outBuf); + EXPECT_TRUE(r); + b58Result[i] = r.value(); + } + else + { + std::string const s = + xrpl::b58_ref::encodeBase58Token(tokType, b256Data.data(), b256Data.size()); + EXPECT_TRUE(s.size()); + b58Result[i] = outBuf.subspan(0, s.size()); + std::ranges::copy(s, b58Result[i].begin()); + } + } + auto const tokenB58SameSize = b58Result[0].size() == b58Result[1].size(); + EXPECT_TRUE(tokenB58SameSize); + if (tokenB58SameSize) + { + auto const tokenB58SameData = + memcmp(b58Result[0].data(), b58Result[1].data(), b58Result[0].size()) == 0; + EXPECT_TRUE(tokenB58SameData); + if (!tokenB58SameData) + { + printAsChar(b58Result[0], b58Result[1]); + } + } + + for (int i = 0; i < 2; ++i) + { + std::span const outBuf{b256ResultBuf[i].data(), b256ResultBuf[i].size()}; + if (i == 0) + { + std::string const in( + b58Result[i].data(), b58Result[i].data() + b58Result[i].size()); + auto const r = xrpl::b58_fast::decodeBase58Token(tokType, in, outBuf); + EXPECT_TRUE(r); + b256Result[i] = r.value(); + } + else + { + std::string const st(b58Result[i].begin(), b58Result[i].end()); + std::string const s = xrpl::b58_ref::decodeBase58Token(st, tokType); + EXPECT_TRUE(s.size()); + b256Result[i] = outBuf.subspan(0, s.size()); + std::ranges::copy(s, b256Result[i].begin()); + } + } + + auto const tokenB256SameSize = b256Result[0].size() == b256Result[1].size(); + EXPECT_TRUE(tokenB256SameSize); + if (tokenB256SameSize) + { + auto const tokenB256SameData = + memcmp(b256Result[0].data(), b256Result[1].data(), b256Result[0].size()) == 0; + EXPECT_TRUE(tokenB256SameData); + if (!tokenB256SameData) + { + printAsInt(b256Result[0], b256Result[1]); + } + } + }; + + auto testIt = [&](xrpl::TokenType const tokType, std::span const& b256Data) { + testRawEncode(b256Data); + testTokenEncode(tokType, b256Data); + }; + + // test every token type with data where every byte is the same and the + // bytes range from 0-255 + for (int i = 0; i < kNumTokenTypeIndexes; ++i) + { + std::array b256DataBuf{}; + auto const [tokType, tokSize] = tokenTypeAndSize(i); + for (int d = 0; d <= 255; ++d) + { + memset(b256DataBuf.data(), d, tokSize); + testIt(tokType, std::span(b256DataBuf.data(), tokSize)); + } + } + + // test with random data + constexpr std::size_t kIters = 100000; + for (int i = 0; i < kIters; ++i) + { + std::array b256DataBuf{}; + auto const [tokType, b256Data] = randomB256TestData(b256DataBuf); + testIt(tokType, b256Data); + } +} + +} // namespace xrpl::test + +#endif // _MSC_VER diff --git a/src/tests/libxrpl/basics/base_uint_test.cpp b/src/tests/libxrpl/basics/base_uint_test.cpp new file mode 100644 index 0000000000..365b43930c --- /dev/null +++ b/src/tests/libxrpl/basics/base_uint_test.cpp @@ -0,0 +1,360 @@ +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { + +// a non-hashing Hasher that just copies the bytes. +// Used to test hash_append in base_uint +template +struct Nonhash +{ + static constexpr auto const kEndian = boost::endian::order::big; + static constexpr std::size_t kWidth = Bits / 8; + + std::array data; + + Nonhash() = default; + + void + operator()(void const* key, std::size_t len) noexcept + { + assert(len == kWidth); + memcpy(data.data(), key, len); + } + + explicit + operator std::size_t() noexcept + { + return kWidth; + } +}; + +struct BaseUintTest : public ::testing::Test +{ + using BaseUInt96 = BaseUInt<96>; + static_assert(std::is_copy_constructible_v); + static_assert(std::is_copy_assignable_v); + + static void + testComparisons() + { + { + static constexpr std::array, 6> kTestArgs{ + {{"0000000000000000", "0000000000000001"}, + {"0000000000000000", "ffffffffffffffff"}, + {"1234567812345678", "2345678923456789"}, + {"8000000000000000", "8000000000000001"}, + {"aaaaaaaaaaaaaaa9", "aaaaaaaaaaaaaaaa"}, + {"fffffffffffffffe", "ffffffffffffffff"}}}; + + for (auto const& arg : kTestArgs) + { + xrpl::BaseUInt<64> const u{arg.first}, v{arg.second}; + // For code readability, we want to use general boolean + // expectations instead of specific EXPECT_LT etc. + EXPECT_TRUE(u < v); + EXPECT_TRUE(u <= v); + EXPECT_TRUE(u != v); + EXPECT_FALSE(u == v); + EXPECT_FALSE(u > v); + EXPECT_FALSE(u >= v); + EXPECT_FALSE(v < u); + EXPECT_FALSE(v <= u); + EXPECT_TRUE(v != u); + EXPECT_FALSE(v == u); + EXPECT_TRUE(v > u); + EXPECT_TRUE(v >= u); + EXPECT_TRUE(u == u); + EXPECT_TRUE(v == v); + } + } + + { + static constexpr std::array, 6> kTestArgs{ + { + {"000000000000000000000000", "000000000000000000000001"}, + {"000000000000000000000000", "ffffffffffffffffffffffff"}, + {"0123456789ab0123456789ab", "123456789abc123456789abc"}, + {"555555555555555555555555", "55555555555a555555555555"}, + {"aaaaaaaaaaaaaaa9aaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaa"}, + {"fffffffffffffffffffffffe", "ffffffffffffffffffffffff"}, + }}; + + for (auto const& arg : kTestArgs) + { + xrpl::BaseUInt<96> const u{arg.first}, v{arg.second}; + EXPECT_TRUE(u < v); + EXPECT_TRUE(u <= v); + EXPECT_TRUE(u != v); + EXPECT_FALSE(u == v); + EXPECT_FALSE(u > v); + EXPECT_FALSE(u >= v); + EXPECT_FALSE(v < u); + EXPECT_FALSE(v <= u); + EXPECT_TRUE(v != u); + EXPECT_FALSE(v == u); + EXPECT_TRUE(v > u); + EXPECT_TRUE(v >= u); + EXPECT_TRUE(u == u); + EXPECT_TRUE(v == v); + } + } + } +}; + +TEST_F(BaseUintTest, base_uint) +{ + static_assert(!std::is_constructible_v>); + static_assert(!std::is_assignable_v>); + + testComparisons(); + + // used to verify set insertion (hashing required) + std::unordered_set> uset; + + Blob const raw{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + EXPECT_EQ(BaseUInt96::kBytes, raw.size()); + + BaseUInt96 u = BaseUInt96::fromRaw(raw); + uset.insert(u); + EXPECT_EQ(raw.size(), u.size()); + EXPECT_EQ(to_string(u), "0102030405060708090A0B0C"); + EXPECT_EQ(toShortString(u), "01020304..."); + EXPECT_EQ(*u.data(), 1); + EXPECT_EQ(u.signum(), 1); + EXPECT_FALSE(!u); + EXPECT_FALSE(u.isZero()); + EXPECT_TRUE(u.isNonZero()); + unsigned char t = 0; + for (auto& d : u) + { + EXPECT_EQ(d, ++t); + } + + // Test hash_append by "hashing" with a no-op hasher (h) + // and then extracting the bytes that were written during hashing + // back into another base_uint (w) for comparison with the original + Nonhash<96> h{}; + hash_append(h, u); + BaseUInt96 const w = + BaseUInt96::fromRaw(std::vector(h.data.begin(), h.data.end())); + EXPECT_EQ(w, u); + + BaseUInt96 v{~u}; + uset.insert(v); + EXPECT_EQ(to_string(v), "FEFDFCFBFAF9F8F7F6F5F4F3"); + EXPECT_EQ(toShortString(v), "FEFDFCFB..."); + EXPECT_EQ(*v.data(), 0xfe); + EXPECT_EQ(v.signum(), 1); + EXPECT_FALSE(!v); + EXPECT_FALSE(v.isZero()); + EXPECT_TRUE(v.isNonZero()); + + t = 0xff; + for (auto& d : v) + { + EXPECT_EQ(d, --t); + } + + EXPECT_LT(u, v); + EXPECT_GT(v, u); + + v = u; + EXPECT_EQ(v, u); + + BaseUInt96 z{beast::kZero}; + uset.insert(z); + EXPECT_EQ(to_string(z), "000000000000000000000000"); + EXPECT_EQ(toShortString(z), "00000000..."); + EXPECT_EQ(*z.data(), 0); + EXPECT_EQ(*z.begin(), 0); + EXPECT_EQ(*std::prev(z.end(), 1), 0); + EXPECT_EQ(z.signum(), 0); + EXPECT_TRUE(!z); + EXPECT_TRUE(z.isZero()); + EXPECT_FALSE(z.isNonZero()); + for (auto& d : z) + { + EXPECT_EQ(d, 0); + } + + BaseUInt96 n{z}; + n++; + EXPECT_EQ(n, BaseUInt96(1)); + n--; + EXPECT_EQ(n, beast::kZero); + EXPECT_EQ(n, z); + n--; + EXPECT_EQ(to_string(n), "FFFFFFFFFFFFFFFFFFFFFFFF"); + EXPECT_EQ(toShortString(n), "FFFFFFFF..."); + n = beast::kZero; + EXPECT_EQ(n, z); + + BaseUInt96 zp1{z}; + zp1++; + BaseUInt96 zm1{z}; + zm1--; + BaseUInt96 const x{zm1 ^ zp1}; + uset.insert(x); + EXPECT_EQ(to_string(x), "FFFFFFFFFFFFFFFFFFFFFFFE") << to_string(x); + EXPECT_EQ(toShortString(x), "FFFFFFFF...") << toShortString(x); + + EXPECT_EQ(uset.size(), 4); + + BaseUInt96 tmp; + EXPECT_TRUE(tmp.parseHex(to_string(u))); + EXPECT_EQ(tmp, u); + tmp = z; + + // fails with extra char + EXPECT_FALSE(tmp.parseHex("A" + to_string(u))); + tmp = z; + + // fails with extra char at end + EXPECT_FALSE(tmp.parseHex(to_string(u) + "A")); + + // fails with a non-hex character at some point in the string: + tmp = z; + + for (std::size_t i = 0; i != 24; ++i) + { + std::string x = to_string(z); + x[i] = ('G' + (i % 10)); + EXPECT_FALSE(tmp.parseHex(x)); + } + + // Walking 1s: + for (std::size_t i = 0; i != 24; ++i) + { + std::string s1 = "000000000000000000000000"; + s1[i] = '1'; + + EXPECT_TRUE(tmp.parseHex(s1)); + EXPECT_EQ(to_string(tmp), s1); + } + + // Walking 0s: + for (std::size_t i = 0; i != 24; ++i) + { + std::string s1 = "111111111111111111111111"; + s1[i] = '0'; + + EXPECT_TRUE(tmp.parseHex(s1)); + EXPECT_EQ(to_string(tmp), s1); + } + + // Constexpr constructors + { + static_assert(BaseUInt96{}.signum() == 0); + static_assert(BaseUInt96("0").signum() == 0); + static_assert(BaseUInt96("000000000000000000000000").signum() == 0); + static_assert(BaseUInt96("000000000000000000000001").signum() == 1); + static_assert(BaseUInt96("800000000000000000000000").signum() == 1); + +// Everything within the #if should fail during compilation. +#if 0 + // Too few characters + static_assert(BaseUInt96("00000000000000000000000").signum() == 0); + + // Too many characters + static_assert(BaseUInt96("0000000000000000000000000").signum() == 0); + + // Non-hex characters + static_assert(BaseUInt96("00000000000000000000000 ").signum() == 1); + static_assert(BaseUInt96("00000000000000000000000/").signum() == 1); + static_assert(BaseUInt96("00000000000000000000000:").signum() == 1); + static_assert(BaseUInt96("00000000000000000000000@").signum() == 1); + static_assert(BaseUInt96("00000000000000000000000G").signum() == 1); + static_assert(BaseUInt96("00000000000000000000000`").signum() == 1); + static_assert(BaseUInt96("00000000000000000000000g").signum() == 1); + static_assert(BaseUInt96("00000000000000000000000~").signum() == 1); +#endif // 0 + + // Using the constexpr constructor in a non-constexpr context + // with an error in the parsing throws an exception. + { + // Invalid length for string. + bool caught = false; + try + { + // Try to prevent constant evaluation. + std::vector str(23, '7'); + std::string_view const sView(str.data(), str.size()); + [[maybe_unused]] BaseUInt96 const t96(sView); + } + catch (std::invalid_argument const& e) + { + EXPECT_EQ(e.what(), std::string("invalid length for hex string")); + caught = true; + } + EXPECT_TRUE(caught); + } + { + // Invalid character in string. + bool caught = false; + try + { + // Try to prevent constant evaluation. + std::vector str(23, '7'); + str.push_back('G'); + std::string_view const sView(str.data(), str.size()); + [[maybe_unused]] BaseUInt96 const t96(sView); + } + catch (std::range_error const& e) + { + EXPECT_EQ(e.what(), std::string("invalid hex character")); + caught = true; + } + EXPECT_TRUE(caught); + } + + // Verify that constexpr base_uints interpret a string the same + // way parseHex() does. + struct StrBaseUInt + { + char const* const str; + BaseUInt96 tst; + + constexpr StrBaseUInt(char const* s) : str(s), tst(s) + { + } + }; + constexpr StrBaseUInt kTestCases[] = { + "000000000000000000000000", + "000000000000000000000001", + "fedcba9876543210ABCDEF91", + "19FEDCBA0123456789abcdef", + "800000000000000000000000", + "fFfFfFfFfFfFfFfFfFfFfFfF", + }; + + for (StrBaseUInt const& t : kTestCases) + { + BaseUInt96 t96; + EXPECT_TRUE(t96.parseHex(t.str)); + EXPECT_EQ(t96, t.tst); + } + } +} + +} // namespace xrpl::test diff --git a/src/test/basics/hardened_hash_test.cpp b/src/tests/libxrpl/basics/hardened_hash.cpp similarity index 84% rename from src/test/basics/hardened_hash_test.cpp rename to src/tests/libxrpl/basics/hardened_hash.cpp index 8b10967932..4f51ce4305 100644 --- a/src/test/basics/hardened_hash_test.cpp +++ b/src/tests/libxrpl/basics/hardened_hash.cpp @@ -1,6 +1,8 @@ #include + #include -#include + +#include #include #include @@ -153,20 +155,20 @@ static_assert(sha256_t::kBits == 256, "sha256_t must have 256 bits"); namespace xrpl { -class hardened_hash_test : public beast::unit_test::Suite +class HardenedHashTest : public ::testing::Test { public: template - void + static void check() { T t{}; HardenedHash<>()(t); - pass(); + SUCCEED(); } template