mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-12 17:50:26 +00:00
Compare commits
13 Commits
a1q123456/
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8306ac7710 | ||
|
|
fd2cc6dcb3 | ||
|
|
c7adb215ed | ||
|
|
71ee0f400b | ||
|
|
58af1e6f18 | ||
|
|
e372c45836 | ||
|
|
2ebc96a4a6 | ||
|
|
c5a6de6ef7 | ||
|
|
f5e63f8a91 | ||
|
|
7a153a2bce | ||
|
|
f59d5c0894 | ||
|
|
bfde271609 | ||
|
|
d07d2aba2e |
49
.clang-tidy
49
.clang-tidy
@@ -9,56 +9,33 @@ 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,
|
||||
|
||||
misc-*,
|
||||
-misc-anonymous-namespace-in-header,
|
||||
-misc-confusable-identifiers,
|
||||
-misc-coroutine-hostile-raii,
|
||||
-misc-misleading-bidirectional,
|
||||
-misc-misleading-identifier,
|
||||
-misc-multiple-inheritance,
|
||||
-misc-new-delete-overloads,
|
||||
-misc-no-recursion,
|
||||
-misc-non-copyable-objects,
|
||||
-misc-non-private-member-variables-in-classes,
|
||||
-misc-override-with-different-visibility,
|
||||
-misc-predictable-rand,
|
||||
-misc-unconventional-assign-operator,
|
||||
-misc-uniqueptr-reset-release,
|
||||
-misc-unused-parameters,
|
||||
-misc-use-anonymous-namespace,
|
||||
-misc-use-internal-linkage,
|
||||
@@ -66,40 +43,14 @@ Checks: "-*,
|
||||
modernize-*,
|
||||
-modernize-avoid-c-arrays,
|
||||
-modernize-avoid-c-style-cast,
|
||||
-modernize-avoid-setjmp-longjmp,
|
||||
-modernize-avoid-variadic-functions,
|
||||
-modernize-deprecated-ios-base-aliases,
|
||||
-modernize-loop-convert,
|
||||
-modernize-macro-to-enum,
|
||||
-modernize-min-max-use-initializer-list,
|
||||
-modernize-raw-string-literal,
|
||||
-modernize-redundant-void-arg,
|
||||
-modernize-replace-auto-ptr,
|
||||
-modernize-replace-disallow-copy-and-assign-macro,
|
||||
-modernize-replace-random-shuffle,
|
||||
-modernize-return-braced-init-list,
|
||||
-modernize-shrink-to-fit,
|
||||
-modernize-use-bool-literals,
|
||||
-modernize-use-default-member-init,
|
||||
-modernize-use-integer-sign-comparison,
|
||||
-modernize-use-noexcept,
|
||||
-modernize-use-nullptr,
|
||||
-modernize-use-std-format,
|
||||
-modernize-use-std-print,
|
||||
-modernize-use-trailing-return-type,
|
||||
-modernize-use-transparent-functors,
|
||||
-modernize-use-uncaught-exceptions,
|
||||
|
||||
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,
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
15
docs/build/environment.md
vendored
15
docs/build/environment.md
vendored
@@ -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
|
||||
|
||||
40
docs/build/nix.md
vendored
40
docs/build/nix.md
vendored
@@ -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
|
||||
|
||||
|
||||
@@ -641,6 +641,9 @@ template <class T>
|
||||
T*
|
||||
SharedWeakUnion<T>::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<T*>(tp_ & kPtrMask);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -55,37 +54,43 @@ namespace detail {
|
||||
* compile time. Doing it at runtime would be pretty wasteful and
|
||||
* inefficient.
|
||||
*/
|
||||
constexpr std::size_t kInt64Digits = 20;
|
||||
consteval std::array<std::uint64_t, kInt64Digits>
|
||||
constexpr std::size_t kUint64Digits = 20;
|
||||
[[maybe_unused]] constexpr std::size_t kUint128Digits = 39;
|
||||
|
||||
template <typename T, std::size_t Digits>
|
||||
consteval std::array<T, Digits>
|
||||
buildPowersOfTen()
|
||||
{
|
||||
std::array<std::uint64_t, kInt64Digits> result{};
|
||||
std::array<T, Digits> 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<std::uint64_t>::max() / 10)
|
||||
if (power > std::numeric_limits<T>::max() / 10)
|
||||
throw std::logic_error("Power of 10 table is too big");
|
||||
}
|
||||
result[exponent] = power;
|
||||
if (power < std::numeric_limits<std::uint64_t>::max() / 10)
|
||||
throw std::logic_error("Power of 10 table is not big enough for the uint64_t type");
|
||||
if (power < std::numeric_limits<T>::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<std::uint64_t, detail::kInt64Digits> kPowerOfTen = detail::buildPowersOfTen();
|
||||
template <typename T = std::uint64_t, std::size_t Digits = detail::kUint64Digits>
|
||||
constexpr std::array<T, Digits> kPowerOfTenImpl = detail::buildPowersOfTen<T, Digits>();
|
||||
|
||||
constexpr auto kPowerOfTen = kPowerOfTenImpl<std::uint64_t, detail::kUint64Digits>;
|
||||
|
||||
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.
|
||||
*
|
||||
@@ -124,17 +129,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 +170,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<MantissaScale> const&
|
||||
getAllScales();
|
||||
getAllScales()
|
||||
{
|
||||
static std::set<MantissaRange::MantissaScale> 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 +201,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,17 +231,16 @@ 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<MantissaScale, MantissaRange> const&
|
||||
getRanges();
|
||||
};
|
||||
|
||||
// Like std::integral, but only 64-bit integral types.
|
||||
@@ -554,9 +593,15 @@ private:
|
||||
// changing the values inside the range.
|
||||
static thread_local std::reference_wrapper<MantissaRange const> kRange;
|
||||
|
||||
class Guard;
|
||||
|
||||
void
|
||||
normalize(MantissaRange const& 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
|
||||
@@ -571,7 +616,7 @@ private:
|
||||
int& exponent,
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled);
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix);
|
||||
|
||||
template <class T>
|
||||
friend void
|
||||
@@ -581,7 +626,7 @@ private:
|
||||
int& exponent,
|
||||
MantissaRange::rep const& minMantissa,
|
||||
MantissaRange::rep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix,
|
||||
bool dropped);
|
||||
|
||||
[[nodiscard]] bool
|
||||
@@ -599,8 +644,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
|
||||
@@ -862,21 +905,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
|
||||
{
|
||||
|
||||
@@ -595,7 +595,7 @@ template <std::size_t Bits, typename Tag>
|
||||
[[nodiscard]] constexpr bool
|
||||
operator==(BaseUInt<Bits, Tag> const& lhs, BaseUInt<Bits, Tag> const& rhs)
|
||||
{
|
||||
return (lhs <=> rhs) == 0;
|
||||
return (lhs <=> rhs) == 0; // NOLINT(modernize-use-nullptr)
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
/*
|
||||
* MSVC 2019 version 16.9.0 added [[nodiscard]] to the std comparison
|
||||
* operator() functions. boost::bimap checks that the comparator is a
|
||||
* BinaryFunction, in part by calling the function and ignoring the value.
|
||||
* These two things don't play well together. These wrapper classes simply
|
||||
* strip [[nodiscard]] from operator() for use in boost::bimap.
|
||||
*
|
||||
* See also:
|
||||
* https://www.boost.org/doc/libs/1_75_0/libs/bimap/doc/html/boost_bimap/the_tutorial/controlling_collection_types.html
|
||||
*/
|
||||
|
||||
template <class T = void>
|
||||
struct less
|
||||
{
|
||||
using result_type = bool;
|
||||
|
||||
constexpr bool
|
||||
operator()(T const& left, T const& right) const
|
||||
{
|
||||
return std::less<T>()(left, right);
|
||||
}
|
||||
};
|
||||
|
||||
template <class T = void>
|
||||
struct equal_to
|
||||
{
|
||||
using result_type = bool;
|
||||
|
||||
constexpr bool
|
||||
operator()(T const& left, T const& right) const
|
||||
{
|
||||
return std::equal_to<T>()(left, right);
|
||||
}
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
template <class T = void>
|
||||
using less = std::less<T>;
|
||||
|
||||
template <class T = void>
|
||||
using equal_to = std::equal_to<T>;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -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<std::size_t> 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_,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h> // IWYU pragma: keep
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -359,6 +359,7 @@ private:
|
||||
deleteElement(Element const* p)
|
||||
{
|
||||
ElementAllocatorTraits::destroy(config_.alloc(), p);
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
|
||||
ElementAllocatorTraits::deallocate(config_.alloc(), const_cast<Element*>(p), 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -528,6 +528,7 @@ private:
|
||||
deleteElement(Element const* p)
|
||||
{
|
||||
ElementAllocatorTraits::destroy(config_.alloc(), p);
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
|
||||
ElementAllocatorTraits::deallocate(config_.alloc(), const_cast<Element*>(p), 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ public:
|
||||
operator=(NodePtr node)
|
||||
{
|
||||
node_ = node;
|
||||
return static_cast<LockFreeStackIterator&>(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
LockFreeStackIterator&
|
||||
|
||||
@@ -392,36 +392,12 @@ hash_append(Hasher& h, boost::container::flat_set<Key, Compare, Alloc> const& v)
|
||||
}
|
||||
// tuple
|
||||
|
||||
namespace detail {
|
||||
|
||||
inline void
|
||||
forEachItem(...) noexcept
|
||||
{
|
||||
}
|
||||
|
||||
template <class Hasher, class T>
|
||||
inline int
|
||||
hashOne(Hasher& h, T const& t) noexcept
|
||||
{
|
||||
hash_append(h, t);
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <class Hasher, class... T, std::size_t... I>
|
||||
inline void
|
||||
tuple_hash(Hasher& h, std::tuple<T...> const& t, std::index_sequence<I...>) noexcept
|
||||
{
|
||||
for_each_item(hash_one(h, std::get<I>(t))...);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <class Hasher, class... T>
|
||||
inline void
|
||||
hash_append(Hasher& h, std::tuple<T...> const& t) noexcept
|
||||
requires(!IsContiguouslyHashable<std::tuple<T...>, Hasher>::value)
|
||||
{
|
||||
detail::tuple_hash(h, t, std::index_sequence_for<T...>{});
|
||||
std::apply([&h](auto const&... item) { (hash_append(h, item), ...); }, t);
|
||||
}
|
||||
|
||||
// shared_ptr
|
||||
|
||||
@@ -49,6 +49,11 @@ public:
|
||||
impl_->set(value);
|
||||
}
|
||||
|
||||
// This is a write-through handle: assignment sets the value of the
|
||||
// referenced metric. It is const-qualified and returns Gauge const&
|
||||
// (a non-const Gauge& would require a const_cast), so it does not follow
|
||||
// the conventional assignment-operator signature.
|
||||
// NOLINTNEXTLINE(misc-unconventional-assign-operator)
|
||||
Gauge const&
|
||||
operator=(value_type value) const
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -26,9 +26,7 @@ struct Zero
|
||||
explicit Zero() = default;
|
||||
};
|
||||
|
||||
namespace {
|
||||
constexpr Zero kZero{};
|
||||
} // namespace
|
||||
inline constexpr Zero kZero{};
|
||||
|
||||
/** Default implementation of signum calls the method on the class. */
|
||||
template <typename T>
|
||||
|
||||
@@ -127,7 +127,7 @@ private:
|
||||
}
|
||||
|
||||
[[nodiscard]] HashRouterFlags
|
||||
getFlags(void) const
|
||||
getFlags() const
|
||||
{
|
||||
return flags_;
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ private:
|
||||
Location end,
|
||||
unsigned int& unicode);
|
||||
bool
|
||||
addError(std::string const& message, Token& token, Location extra = 0);
|
||||
addError(std::string const& message, Token& token, Location extra = nullptr);
|
||||
bool
|
||||
recoverFromError(TokenType skipUntilToken);
|
||||
bool
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/OwnerCounts.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Issue.h> // IWYU pragma: keep
|
||||
@@ -11,6 +12,7 @@
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STVector256.h>
|
||||
|
||||
#include <cstdint>
|
||||
@@ -288,7 +290,7 @@ 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)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -411,6 +413,23 @@ public:
|
||||
emptyDirDelete(Keylet const& directory);
|
||||
};
|
||||
|
||||
/** 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;
|
||||
};
|
||||
|
||||
namespace directory {
|
||||
/** Helper functions for managing low-level directory operations.
|
||||
These are not part of the ApplyView interface.
|
||||
|
||||
@@ -82,7 +82,7 @@ private:
|
||||
using txs_map = std::map<
|
||||
key_type,
|
||||
TxData,
|
||||
std::less<key_type>,
|
||||
std::less<>,
|
||||
boost::container::pmr::polymorphic_allocator<std::pair<key_type const, TxData>>>;
|
||||
|
||||
// monotonic_resource_ must outlive `items_`. Make a pointer so it may be
|
||||
|
||||
70
include/xrpl/ledger/OwnerCounts.h
Normal file
70
include/xrpl/ledger/OwnerCounts.h
Normal file
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STInteger.h> // IWYU pragma: keep
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
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<std::int64_t>(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<std::uint32_t>::max())
|
||||
return std::numeric_limits<std::uint32_t>::max(); // LCOV_EXCL_LINE
|
||||
return static_cast<std::uint32_t>(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
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/OwnerCounts.h>
|
||||
#include <xrpl/ledger/RawView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/detail/ApplyViewBase.h>
|
||||
@@ -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<std::uint32_t>
|
||||
[[nodiscard]] std::optional<OwnerCounts>
|
||||
ownerCount(AccountID const& id) const;
|
||||
|
||||
void
|
||||
@@ -124,7 +125,7 @@ private:
|
||||
|
||||
std::map<KeyIOU, ValueIOU> creditsIOU_;
|
||||
std::map<MPTID, IssuerValueMPT> creditsMPT_;
|
||||
std::map<AccountID, std::uint32_t> ownerCounts_;
|
||||
std::map<AccountID, OwnerCounts> ownerCounts_;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
@@ -218,10 +219,11 @@ 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.
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/beast/hash/uhash.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/OwnerCounts.h>
|
||||
#include <xrpl/ledger/detail/ReadViewFwdRange.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Fees.h>
|
||||
@@ -13,6 +14,7 @@
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
@@ -189,8 +191,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;
|
||||
}
|
||||
|
||||
@@ -210,8 +210,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,
|
||||
|
||||
@@ -105,7 +105,7 @@ private:
|
||||
using items_t = std::map<
|
||||
key_type,
|
||||
SleAction,
|
||||
std::less<key_type>,
|
||||
std::less<>,
|
||||
boost::container::pmr::polymorphic_allocator<std::pair<key_type const, SleAction>>>;
|
||||
// monotonic_resource_ must outlive `items_`. Make a pointer so it may be
|
||||
// easily moved.
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
@@ -26,21 +27,293 @@ namespace xrpl {
|
||||
[[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. */
|
||||
struct Adjustment
|
||||
{
|
||||
std::int32_t ownerCountDelta = 0;
|
||||
std::int32_t accountCountDelta = 0;
|
||||
};
|
||||
|
||||
/** 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
|
||||
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);
|
||||
|
||||
/** 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<AccountID> 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<AccountID> 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
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
@@ -22,17 +23,15 @@
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
template <ValidIssueType T>
|
||||
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<Issue>(
|
||||
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<Issue>(
|
||||
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<Issue>(
|
||||
initialBalance.get<Issue>().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>(
|
||||
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<Issue>(
|
||||
// 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<Issue>(
|
||||
// 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<Issue>(
|
||||
template <>
|
||||
inline TER
|
||||
escrowUnlockApplyHelper<MPTIssue>(
|
||||
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<MPTIssue>(
|
||||
|
||||
auto const mptID = amount.get<MPTIssue>().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<MPTIssue>(
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue);
|
||||
|
||||
[[nodiscard]] TER
|
||||
authorizeMPToken(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
XRPAmount const& priorBalance,
|
||||
MPTID const& mptIssuanceID,
|
||||
AccountID const& account,
|
||||
@@ -117,7 +117,7 @@ requireAuth(
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
enforceMPTokenAuthorization(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
MPTID const& mptIssuanceID,
|
||||
AccountID const& account,
|
||||
XRPAmount const& priorBalance,
|
||||
@@ -203,7 +203,7 @@ canMPTTradeAndTransfer(
|
||||
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
MPTIssue const& mptIssue,
|
||||
@@ -211,7 +211,7 @@ addEmptyHolding(
|
||||
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
AccountID const& accountID,
|
||||
MPTIssue const& mptIssue,
|
||||
beast::Journal journal);
|
||||
@@ -243,6 +243,7 @@ createMPToken(
|
||||
ApplyView& view,
|
||||
MPTID const& mptIssuanceID,
|
||||
AccountID const& account,
|
||||
SLE::ref sponsorSle,
|
||||
std::uint32_t const flags);
|
||||
|
||||
TER
|
||||
@@ -250,6 +251,7 @@ checkCreateMPT(
|
||||
xrpl::ApplyView& view,
|
||||
xrpl::MPTIssue const& mptIssue,
|
||||
xrpl::AccountID const& holder,
|
||||
SLE::ref sponsorSle,
|
||||
beast::Journal j);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
31
include/xrpl/ledger/helpers/OracleHelpers.h
Normal file
31
include/xrpl/ledger/helpers/OracleHelpers.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STArray.h> // IWYU pragma: keep
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
constexpr std::uint32_t kMinOracleReserveCount = 1;
|
||||
constexpr std::uint32_t kMaxOracleReserveCount = 2;
|
||||
constexpr std::size_t kOracleReserveCountThreshold = 5;
|
||||
|
||||
template <typename T>
|
||||
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
|
||||
@@ -156,6 +156,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 +179,7 @@ issueIOU(
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
Issue const& issue,
|
||||
SLE::ref sponsorSle,
|
||||
beast::Journal j);
|
||||
|
||||
[[nodiscard]] TER
|
||||
@@ -235,7 +237,7 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc
|
||||
/// 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,7 +245,7 @@ addEmptyHolding(
|
||||
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
AccountID const& accountID,
|
||||
Issue const& issue,
|
||||
beast::Journal journal);
|
||||
|
||||
184
include/xrpl/ledger/helpers/SponsorHelpers.h
Normal file
184
include/xrpl/ledger/helpers/SponsorHelpers.h
Normal file
@@ -0,0 +1,184 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <optional>
|
||||
|
||||
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<AccountID>
|
||||
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<SLE::pointer, TER>
|
||||
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<SLE::const_pointer, TER>
|
||||
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<SLE::pointer, TER>
|
||||
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<AccountID>
|
||||
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
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
@@ -302,7 +303,7 @@ canAddHolding(ReadView const& view, Asset const& asset);
|
||||
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
Asset const& asset,
|
||||
@@ -310,7 +311,7 @@ addEmptyHolding(
|
||||
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
AccountID const& accountID,
|
||||
Asset const& asset,
|
||||
beast::Journal journal);
|
||||
@@ -371,6 +372,7 @@ accountSend(
|
||||
AccountID const& to,
|
||||
STAmount const& saAmount,
|
||||
beast::Journal j,
|
||||
SLE::ref sponsorSle = {},
|
||||
WaiveTransferFee waiveFee = WaiveTransferFee::No,
|
||||
AllowMPTOverflow allowOverflow = AllowMPTOverflow::No);
|
||||
|
||||
|
||||
@@ -34,11 +34,11 @@ public:
|
||||
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_;
|
||||
};
|
||||
|
||||
|
||||
@@ -74,9 +74,9 @@ operator==(Book const& lhs, Book const& rhs)
|
||||
[[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
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -39,13 +38,13 @@ 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.
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -142,6 +142,10 @@ ticket(uint256 const& key)
|
||||
Keylet
|
||||
signerList(AccountID const& account) noexcept;
|
||||
|
||||
/** A Sponsorship */
|
||||
Keylet
|
||||
sponsorship(AccountID const& sponsor, AccountID const& sponsee) noexcept;
|
||||
|
||||
/** A Check */
|
||||
/** @{ */
|
||||
Keylet
|
||||
|
||||
@@ -84,7 +84,7 @@ operator==(Issue const& lhs, Issue const& rhs)
|
||||
[[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))
|
||||
|
||||
@@ -208,7 +208,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
|
||||
|
||||
|
||||
@@ -229,10 +229,10 @@ public:
|
||||
[[nodiscard]] AccountID
|
||||
getAccountID(SField const& field) const;
|
||||
|
||||
/** The account responsible for the fee and authorization: the delegate when
|
||||
/** 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;
|
||||
@@ -555,7 +555,11 @@ public:
|
||||
ValueProxy&
|
||||
operator=(ValueProxy const&) = delete;
|
||||
|
||||
// Write-through proxy: assignment sets the referenced field to the given
|
||||
// value, so it intentionally takes the assigned value rather than a
|
||||
// ValueProxy.
|
||||
template <class U>
|
||||
// NOLINTNEXTLINE(misc-unconventional-assign-operator)
|
||||
ValueProxy&
|
||||
operator=(U&& u)
|
||||
requires(std::is_assignable_v<T, U>);
|
||||
@@ -800,6 +804,7 @@ STObject::Proxy<T>::assign(U&& u)
|
||||
|
||||
template <class T>
|
||||
template <class U>
|
||||
// NOLINTNEXTLINE(misc-unconventional-assign-operator)
|
||||
STObject::ValueProxy<T>&
|
||||
STObject::ValueProxy<T>::operator=(U&& u)
|
||||
requires(std::is_assignable_v<T, U>)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -141,6 +141,9 @@ public:
|
||||
[[nodiscard]] std::vector<uint256> const&
|
||||
getBatchTransactionIDs() const;
|
||||
|
||||
[[nodiscard]] AccountID
|
||||
getFeePayerID() const;
|
||||
|
||||
private:
|
||||
/** Check the signature.
|
||||
@param rules The current ledger rules.
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -102,7 +102,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 +142,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 +216,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 +459,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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1165,6 +1165,35 @@ TRANSACTION(ttCONFIDENTIAL_MPT_CLAWBACK, 89, ConfidentialMPTClawback,
|
||||
{sfZKProof, SoeRequired},
|
||||
}))
|
||||
|
||||
/** This transaction transfers sponsorship on an object/account. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/sponsor/SponsorshipTransfer.h>
|
||||
#endif
|
||||
TRANSACTION(ttSPONSORSHIP_TRANSFER, 90, SponsorshipTransfer,
|
||||
Delegation::NotDelegable,
|
||||
featureSponsor,
|
||||
NoPriv,
|
||||
({
|
||||
{sfObjectID, SoeOptional},
|
||||
{sfSponsee, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This transaction creates a Sponsorship object. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/sponsor/SponsorshipSet.h>
|
||||
#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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<SF_UINT32::type::value_type>
|
||||
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<SF_UINT32::type::value_type>
|
||||
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<SF_UINT32::type::value_type>
|
||||
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.
|
||||
@@ -786,6 +858,39 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSponsoredOwnerCount (SoeDefault)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
AccountRootBuilder&
|
||||
setSponsoredOwnerCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfSponsoredOwnerCount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSponsoringOwnerCount (SoeDefault)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
AccountRootBuilder&
|
||||
setSponsoringOwnerCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfSponsoringOwnerCount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSponsoringAccountCount (SoeDefault)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
AccountRootBuilder&
|
||||
setSponsoringAccountCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfSponsoringAccountCount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAMMID (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
|
||||
@@ -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<SF_ACCOUNT::type::value_type>
|
||||
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<SF_ACCOUNT::type::value_type>
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -410,6 +458,28 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfHighSponsor (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RippleStateBuilder&
|
||||
setHighSponsor(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfHighSponsor] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfLowSponsor (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RippleStateBuilder&
|
||||
setLowSponsor(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfLowSponsor] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the completed RippleState wrapper.
|
||||
* @param index The ledger entry index.
|
||||
|
||||
344
include/xrpl/protocol_autogen/ledger_entries/Sponsorship.h
Normal file
344
include/xrpl/protocol_autogen/ledger_entries/Sponsorship.h
Normal file
@@ -0,0 +1,344 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/LedgerEntryBase.h>
|
||||
#include <xrpl/protocol_autogen/LedgerEntryBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
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<SF_AMOUNT::type::value_type>
|
||||
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<SF_AMOUNT::type::value_type>
|
||||
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<SF_UINT32::type::value_type>
|
||||
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<SponsorshipBuilder>
|
||||
{
|
||||
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<typename SF_UINT256::type::value_type> const& previousTxnID,std::decay_t<typename SF_UINT32::type::value_type> const& previousTxnLgrSeq,std::decay_t<typename SF_ACCOUNT::type::value_type> const& owner,std::decay_t<typename SF_ACCOUNT::type::value_type> const& sponsee,std::decay_t<typename SF_UINT64::type::value_type> const& ownerNode,std::decay_t<typename SF_UINT64::type::value_type> const& sponseeNode)
|
||||
: LedgerEntryBuilderBase<SponsorshipBuilder>(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<typename SF_UINT256::type::value_type> const& value)
|
||||
{
|
||||
object_[sfPreviousTxnID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfPreviousTxnLgrSeq (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipBuilder&
|
||||
setPreviousTxnLgrSeq(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfPreviousTxnLgrSeq] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfOwner (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipBuilder&
|
||||
setOwner(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfOwner] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSponsee (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipBuilder&
|
||||
setSponsee(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfSponsee] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfFeeAmount (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipBuilder&
|
||||
setFeeAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfFeeAmount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfMaxFee (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipBuilder&
|
||||
setMaxFee(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMaxFee] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfRemainingOwnerCount (SoeDefault)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipBuilder&
|
||||
setRemainingOwnerCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfRemainingOwnerCount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfOwnerNode (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipBuilder&
|
||||
setOwnerNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
|
||||
{
|
||||
object_[sfOwnerNode] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSponseeNode (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipBuilder&
|
||||
setSponseeNode(std::decay_t<typename SF_UINT64::type::value_type> 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<SLE>(std::move(object_), index)};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::ledger_entries
|
||||
290
include/xrpl/protocol_autogen/transactions/SponsorshipSet.h
Normal file
290
include/xrpl/protocol_autogen/transactions/SponsorshipSet.h
Normal file
@@ -0,0 +1,290 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBase.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
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<STTx const> 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<SF_ACCOUNT::type::value_type>
|
||||
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<SF_ACCOUNT::type::value_type>
|
||||
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<SF_AMOUNT::type::value_type>
|
||||
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<SF_AMOUNT::type::value_type>
|
||||
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<SF_UINT32::type::value_type>
|
||||
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<SponsorshipSetBuilder>
|
||||
{
|
||||
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<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<SponsorshipSetBuilder>(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<STTx const> 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<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfCounterpartySponsor] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSponsee (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipSetBuilder&
|
||||
setSponsee(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfSponsee] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfFeeAmount (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipSetBuilder&
|
||||
setFeeAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfFeeAmount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfMaxFee (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipSetBuilder&
|
||||
setMaxFee(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMaxFee] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfRemainingOwnerCount (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipSetBuilder&
|
||||
setRemainingOwnerCount(std::decay_t<typename SF_UINT32::type::value_type> 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<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
179
include/xrpl/protocol_autogen/transactions/SponsorshipTransfer.h
Normal file
179
include/xrpl/protocol_autogen/transactions/SponsorshipTransfer.h
Normal file
@@ -0,0 +1,179 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBase.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
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<STTx const> 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<SF_UINT256::type::value_type>
|
||||
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<SF_ACCOUNT::type::value_type>
|
||||
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<SponsorshipTransferBuilder>
|
||||
{
|
||||
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<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<SponsorshipTransferBuilder>(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<STTx const> 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<typename SF_UINT256::type::value_type> const& value)
|
||||
{
|
||||
object_[sfObjectID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSponsee (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipTransferBuilder&
|
||||
setSponsee(std::decay_t<typename SF_ACCOUNT::type::value_type> 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<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
@@ -351,10 +351,9 @@ public:
|
||||
Import& import(iter->second);
|
||||
if (iter->second.whenExpires <= elapsed)
|
||||
{
|
||||
for (auto itemIter(import.items.begin()); itemIter != import.items.end();
|
||||
++itemIter)
|
||||
for (auto& item : import.items)
|
||||
{
|
||||
itemIter->consumer.entry().remoteBalance -= itemIter->balance;
|
||||
item.consumer.entry().remoteBalance -= item.balance;
|
||||
}
|
||||
|
||||
iter = importTable_.erase(iter);
|
||||
|
||||
@@ -103,7 +103,7 @@ public:
|
||||
|
||||
/** generation determines whether cached entry is valid */
|
||||
std::uint32_t
|
||||
getGeneration(void) const
|
||||
getGeneration() const
|
||||
{
|
||||
return gen_;
|
||||
}
|
||||
|
||||
@@ -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<std::uint8_t*>(p)))
|
||||
delete[] p;
|
||||
}
|
||||
|
||||
@@ -507,6 +507,9 @@ TaggedPointer::operator=(TaggedPointer&& other)
|
||||
[[nodiscard]] inline std::pair<std::uint8_t, void*>
|
||||
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<void*>(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<SHAMapHash*>(tp_ & kPtrMask);
|
||||
};
|
||||
|
||||
|
||||
@@ -127,6 +127,15 @@ public:
|
||||
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);
|
||||
|
||||
@@ -10,8 +10,10 @@
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Fees.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/Permissions.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/Units.h>
|
||||
@@ -126,6 +128,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:
|
||||
@@ -298,6 +315,10 @@ public:
|
||||
|
||||
return T::checkGranularSemantics(view, tx, heldGranularPermissions);
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
checkSponsor(ReadView const& view, STTx const& tx);
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
// Interface used by AccountDelete
|
||||
@@ -459,6 +480,9 @@ private:
|
||||
std::pair<TER, XRPAmount>
|
||||
reset(XRPAmount fee);
|
||||
|
||||
static FeePayer
|
||||
getFeePayer(ReadView const& view, STTx const& tx);
|
||||
|
||||
TER
|
||||
consumeSeqProxy(SLE::pointer const& sleAccount);
|
||||
TER
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <xrpl/tx/invariants/NFTInvariant.h>
|
||||
#include <xrpl/tx/invariants/PermissionedDEXInvariant.h>
|
||||
#include <xrpl/tx/invariants/PermissionedDomainInvariant.h>
|
||||
#include <xrpl/tx/invariants/SponsorshipInvariant.h>
|
||||
#include <xrpl/tx/invariants/VaultInvariant.h>
|
||||
|
||||
#include <cstdint>
|
||||
@@ -441,7 +442,9 @@ using InvariantChecks = std::tuple<
|
||||
ValidMPTPayment,
|
||||
ValidAmounts,
|
||||
ValidMPTTransfer,
|
||||
ObjectHasPseudoAccount>;
|
||||
ObjectHasPseudoAccount,
|
||||
SponsorshipOwnerCountsMatch,
|
||||
SponsorshipAccountCountMatchesField>;
|
||||
|
||||
/**
|
||||
* @brief get a tuple of all invariant checks
|
||||
|
||||
61
include/xrpl/tx/invariants/SponsorshipInvariant.h
Normal file
61
include/xrpl/tx/invariants/SponsorshipInvariant.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
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
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <cstdint>
|
||||
@@ -110,7 +111,10 @@ public:
|
||||
send(Args&&... args)
|
||||
{
|
||||
return accountSend(
|
||||
std::forward<Args>(args)..., WaiveTransferFee::Yes, AllowMPTOverflow::Yes);
|
||||
std::forward<Args>(args)...,
|
||||
SLE::pointer(),
|
||||
WaiveTransferFee::Yes,
|
||||
AllowMPTOverflow::Yes);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
|
||||
@@ -234,7 +234,8 @@ template <typename... Args>
|
||||
TER
|
||||
TOffer<TIn, TOut>::send(Args&&... args)
|
||||
{
|
||||
return accountSend(std::forward<Args>(args)..., WaiveTransferFee::No, AllowMPTOverflow::Yes);
|
||||
return accountSend(
|
||||
std::forward<Args>(args)..., SLE::pointer(), WaiveTransferFee::No, AllowMPTOverflow::Yes);
|
||||
}
|
||||
|
||||
template <StepAmount TIn, StepAmount TOut>
|
||||
|
||||
@@ -92,6 +92,7 @@ public:
|
||||
[[nodiscard]] TOffer<TIn, TOut>&
|
||||
tip() const
|
||||
{
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
|
||||
return const_cast<TOfferStreamBase*>(this)->offer_;
|
||||
}
|
||||
|
||||
|
||||
52
include/xrpl/tx/transactors/sponsor/SponsorshipSet.h
Normal file
52
include/xrpl/tx/transactors/sponsor/SponsorshipSet.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
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
|
||||
49
include/xrpl/tx/transactors/sponsor/SponsorshipTransfer.h
Normal file
49
include/xrpl/tx/transactors/sponsor/SponsorshipTransfer.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
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
|
||||
@@ -76,7 +76,7 @@ public:
|
||||
beast::Journal const& j) override;
|
||||
|
||||
static std::expected<MPTID, TER>
|
||||
create(ApplyView& view, beast::Journal journal, MPTCreateArgs const& args);
|
||||
create(ApplyViewContext ctx, beast::Journal journal, MPTCreateArgs const& args);
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -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
|
||||
|
||||
150
nix/devshell.nix
150
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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
@@ -31,74 +30,103 @@ namespace xrpl {
|
||||
|
||||
thread_local Number::RoundingMode Number::mode = Number::RoundingMode::ToNearest;
|
||||
thread_local std::reference_wrapper<MantissaRange const> Number::kRange =
|
||||
MantissaRange::getMantissaRange(MantissaRange::MantissaScale::Large);
|
||||
MantissaRange::Access::mantissaRange(MantissaRange::MantissaScale::Large330);
|
||||
|
||||
std::set<MantissaRange::MantissaScale> const&
|
||||
MantissaRange::getAllScales()
|
||||
std::string
|
||||
to_string(MantissaRange::MantissaScale const& scale)
|
||||
{
|
||||
static std::set<MantissaRange::MantissaScale> 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<MantissaRange::MantissaScale, MantissaRange> const&
|
||||
MantissaRange::getRanges()
|
||||
std::string
|
||||
to_string(Number::RoundingMode const& round)
|
||||
{
|
||||
static auto const kMap = []() {
|
||||
std::unordered_map<MantissaScale, MantissaRange> 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,38 @@ 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 <class T>
|
||||
concept UnsignedMantissa = std::is_unsigned_v<T> || std::is_same_v<T, uint128_t>;
|
||||
|
||||
/** 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 +222,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,6 +259,10 @@ public:
|
||||
unsigned
|
||||
pop() noexcept;
|
||||
|
||||
// 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.
|
||||
*
|
||||
@@ -206,28 +275,37 @@ public:
|
||||
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 <UnsignedMantissa T>
|
||||
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 <UnsignedMantissa T>
|
||||
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 +317,7 @@ private:
|
||||
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
bringIntoRange(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa);
|
||||
bringIntoRange(bool& negative, T& mantissa, int& exponent);
|
||||
};
|
||||
|
||||
inline void
|
||||
@@ -289,6 +367,12 @@ Number::Guard::pop() noexcept
|
||||
return d;
|
||||
}
|
||||
|
||||
inline bool
|
||||
Number::Guard::empty() const noexcept
|
||||
{
|
||||
return digits_ == 0 && !xbit_;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void
|
||||
Number::Guard::doDropDigit(T& mantissa, int& exponent) noexcept
|
||||
@@ -314,50 +398,52 @@ Number::Guard::doDropDigit<uint128_t>(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 <UnsignedMantissa T>
|
||||
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 +464,15 @@ Number::Guard::bringIntoRange(
|
||||
|
||||
template <UnsignedMantissa T>
|
||||
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 +493,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 +512,43 @@ Number::Guard::doRoundUp(
|
||||
}
|
||||
}
|
||||
}
|
||||
bringIntoRange(negative, mantissa, exponent, minMantissa);
|
||||
bringIntoRange(negative, mantissa, exponent);
|
||||
if (exponent > kMaxExponent)
|
||||
Throw<std::overflow_error>(std::string(location));
|
||||
}
|
||||
|
||||
template <UnsignedMantissa T>
|
||||
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 +556,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 +615,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 +638,7 @@ doNormalize(
|
||||
m *= 10;
|
||||
--exponent;
|
||||
}
|
||||
Guard g;
|
||||
Guard g(minMantissa, maxMantissa, cuspRoundingFix);
|
||||
if (negative)
|
||||
g.setNegative();
|
||||
if (dropped)
|
||||
@@ -598,14 +683,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 +698,12 @@ Number::normalize<uint128_t>(
|
||||
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 +715,12 @@ Number::normalize<unsigned long long>(
|
||||
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 +732,27 @@ Number::normalize<unsigned long>(
|
||||
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 +806,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<uint128_t>(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 +915,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<internalrep>(xm);
|
||||
exponent_ = xe;
|
||||
normalize(range);
|
||||
XRPL_ASSERT(isnormal(), "xrpl::Number::operator+= : result is normal");
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -818,14 +1009,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 +1022,12 @@ Number::operator*=(Number const& y)
|
||||
|
||||
xm = static_cast<internalrep>(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 +1063,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 +1111,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 +1195,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<internalrep>(zm);
|
||||
exponent_ = ze;
|
||||
@@ -1035,7 +1216,7 @@ operator rep() const
|
||||
{
|
||||
rep drops = mantissa();
|
||||
int offset = exponent();
|
||||
Guard g;
|
||||
Guard g(kRange);
|
||||
if (drops != 0)
|
||||
{
|
||||
if (negative_)
|
||||
|
||||
@@ -230,9 +230,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&
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <charconv>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <istream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
|
||||
namespace json {
|
||||
// Implementation of class Reader
|
||||
@@ -86,8 +87,8 @@ Reader::parse(char const* beginDoc, char const* endDoc, Value& root)
|
||||
begin_ = beginDoc;
|
||||
end_ = endDoc;
|
||||
current_ = begin_;
|
||||
lastValueEnd_ = 0;
|
||||
lastValue_ = 0;
|
||||
lastValueEnd_ = nullptr;
|
||||
lastValue_ = nullptr;
|
||||
errors_.clear();
|
||||
|
||||
while (!nodes_.empty())
|
||||
@@ -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;
|
||||
}
|
||||
@@ -908,9 +892,8 @@ Reader::getFormattedErrorMessages() const
|
||||
{
|
||||
std::string formattedMessage;
|
||||
|
||||
for (auto itError = errors_.begin(); itError != errors_.end(); ++itError)
|
||||
for (auto const& error : errors_)
|
||||
{
|
||||
ErrorInfo const& error = *itError;
|
||||
formattedMessage += "* " + getLocationLineAndColumn(error.token.start) + "\n";
|
||||
formattedMessage += " " + error.message + "\n";
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ public:
|
||||
if (length == kUnknown)
|
||||
length = (value != nullptr) ? (unsigned int)strlen(value) : 0;
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
|
||||
char* newString = static_cast<char*>(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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -90,7 +94,7 @@ static struct DummyValueAllocatorInitializer
|
||||
// Notes: index_ indicates if the string was allocated when
|
||||
// a string is stored.
|
||||
|
||||
Value::CZString::CZString(int index) : cstr_(0), index_(index)
|
||||
Value::CZString::CZString(int index) : cstr_(nullptr), index_(index)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -103,7 +107,8 @@ Value::CZString::CZString(char const* cstr, DuplicationPolicy allocate)
|
||||
|
||||
Value::CZString::CZString(CZString const& other)
|
||||
: cstr_(
|
||||
other.index_ != static_cast<int>(DuplicationPolicy::NoDuplication) && other.cstr_ != 0
|
||||
other.index_ != static_cast<int>(DuplicationPolicy::NoDuplication) &&
|
||||
other.cstr_ != nullptr
|
||||
? valueAllocator()->makeMemberName(other.cstr_)
|
||||
: other.cstr_)
|
||||
, index_([&]() -> int {
|
||||
@@ -119,7 +124,10 @@ Value::CZString::CZString(CZString const& other)
|
||||
Value::CZString::~CZString()
|
||||
{
|
||||
if ((cstr_ != nullptr) && index_ == static_cast<int>(DuplicationPolicy::Duplicate))
|
||||
{
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
|
||||
valueAllocator()->releaseMemberName(const_cast<char*>(cstr_));
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -187,7 +195,7 @@ Value::Value(ValueType type) : type_(type)
|
||||
break;
|
||||
|
||||
case ValueType::String:
|
||||
value_.stringVal = 0;
|
||||
value_.stringVal = nullptr;
|
||||
break;
|
||||
|
||||
case ValueType::Array:
|
||||
@@ -240,6 +248,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<char*>(value.cStr());
|
||||
}
|
||||
|
||||
@@ -268,7 +277,7 @@ Value::Value(Value const& other) : type_(other.type_)
|
||||
}
|
||||
else
|
||||
{
|
||||
value_.stringVal = 0;
|
||||
value_.stringVal = nullptr;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -405,7 +414,7 @@ operator<(Value const& x, Value const& y)
|
||||
return static_cast<int>(x.value_.boolVal) < static_cast<int>(y.value_.boolVal);
|
||||
|
||||
case ValueType::String:
|
||||
return (x.value_.stringVal == 0 && (y.value_.stringVal != nullptr)) ||
|
||||
return (x.value_.stringVal == nullptr && (y.value_.stringVal != nullptr)) ||
|
||||
((y.value_.stringVal != nullptr) && (x.value_.stringVal != nullptr) &&
|
||||
strcmp(x.value_.stringVal, y.value_.stringVal) < 0);
|
||||
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
#include <xrpl/json/json_forwards.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <charconv>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <ios>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
|
||||
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
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/OwnerCounts.h>
|
||||
#include <xrpl/ledger/RawView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
@@ -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::uint32_t>
|
||||
std::optional<OwnerCounts>
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
@@ -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
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/OwnerCounts.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -15,6 +17,7 @@
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
@@ -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<AccountID> 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<std::uint32_t>::max();
|
||||
totalOwnerCount = std::numeric_limits<std::uint32_t>::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<std::uint32_t>::max())
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.fatal()) << "Reserve count exceeds max!";
|
||||
totalAccountCount = std::numeric_limits<std::uint32_t>::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<std::int64_t>(ownerCountAdj) - sponsoredOwnerCount + sponsoringOwnerCount;
|
||||
|
||||
if (deltaCount > std::numeric_limits<std::int32_t>::max())
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
deltaCount = std::numeric_limits<std::int32_t>::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<std::int32_t>::min())
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
deltaCount = std::numeric_limits<std::int32_t>::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<std::int32_t>::max(),
|
||||
"xrpl::increaseOwnerCount : count in signed delta range");
|
||||
if (count == 0 || count > std::numeric_limits<std::int32_t>::max())
|
||||
return; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCountSigned(view, accountSle, sponsorSle, static_cast<std::int32_t>(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<std::int32_t>::max(),
|
||||
"xrpl::decreaseOwnerCount : count in signed delta range");
|
||||
if (count == 0 || count > std::numeric_limits<std::int32_t>::max())
|
||||
return; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCountSigned(view, accountSle, sponsorSle, -static_cast<std::int32_t>(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<std::uint32_t>(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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
@@ -11,6 +10,7 @@
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -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<AccountID> 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<SLE>(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;
|
||||
}
|
||||
|
||||
@@ -269,11 +269,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)
|
||||
@@ -411,21 +407,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 +452,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 +490,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 +507,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 +623,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 +726,11 @@ repairNFTokenDirectoryLinks(ApplyView& view, AccountID const& owner)
|
||||
auto const newPrev = view.peek(Keylet(ltNFTOKEN_PAGE, *prevLink));
|
||||
if (!newPrev)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
Throw<std::runtime_error>(
|
||||
"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 +916,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 +968,7 @@ tokenOfferCreateApply(
|
||||
}
|
||||
|
||||
// Update owner count.
|
||||
adjustOwnerCount(view, view.peek(acctKeylet), 1, j);
|
||||
increaseOwnerCount(view, acctID, {}, 1, j);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/AmountConversions.h>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
345
src/libxrpl/ledger/helpers/SponsorHelpers.cpp
Normal file
345
src/libxrpl/ledger/helpers/SponsorHelpers.cpp
Normal file
@@ -0,0 +1,345 @@
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/OracleHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STArray.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <optional>
|
||||
#include <unordered_set>
|
||||
|
||||
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<TxType> 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<AccountID>
|
||||
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<SLE::pointer, TER>
|
||||
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<SLE::const_pointer, TER>
|
||||
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<SLE::pointer, TER>
|
||||
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<AccountID>
|
||||
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<std::uint32_t>(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
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
@@ -571,7 +572,7 @@ canAddHolding(ReadView const& view, Asset const& asset)
|
||||
|
||||
TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
Asset const& asset,
|
||||
@@ -579,21 +580,21 @@ addEmptyHolding(
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(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(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
|
||||
return removeEmptyHolding(view, accountID, issue, journal);
|
||||
return removeEmptyHolding(ctx, accountID, issue, journal);
|
||||
},
|
||||
asset.value());
|
||||
}
|
||||
@@ -647,6 +648,7 @@ directSendNoFeeIOU(
|
||||
AccountID const& uReceiverID,
|
||||
STAmount const& saAmount,
|
||||
bool bCheckIssuer,
|
||||
SLE::ref sponsorSle,
|
||||
beast::Journal j)
|
||||
{
|
||||
AccountID const& issuer = saAmount.getIssuer();
|
||||
@@ -717,7 +719,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);
|
||||
@@ -780,6 +787,7 @@ directSendNoFeeIOU(
|
||||
saReceiverLimit,
|
||||
0,
|
||||
0,
|
||||
sponsorSle,
|
||||
j);
|
||||
}
|
||||
|
||||
@@ -794,6 +802,7 @@ directSendNoLimitIOU(
|
||||
STAmount const& saAmount,
|
||||
STAmount& saActual,
|
||||
beast::Journal j,
|
||||
SLE::ref sponsorSle,
|
||||
WaiveTransferFee waiveFee)
|
||||
{
|
||||
auto const& issuer = saAmount.getIssuer();
|
||||
@@ -806,7 +815,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;
|
||||
@@ -824,10 +834,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;
|
||||
}
|
||||
@@ -870,7 +882,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;
|
||||
@@ -894,14 +907,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;
|
||||
}
|
||||
|
||||
@@ -915,6 +928,7 @@ accountSendIOU(
|
||||
AccountID const& uReceiverID,
|
||||
STAmount const& saAmount,
|
||||
beast::Journal j,
|
||||
SLE::ref sponsorSle,
|
||||
WaiveTransferFee waiveFee)
|
||||
{
|
||||
if (view.rules().enabled(fixAMMv1_1))
|
||||
@@ -946,7 +960,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.
|
||||
@@ -1475,7 +1490,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");
|
||||
@@ -1490,12 +1505,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(
|
||||
|
||||
@@ -370,10 +370,10 @@ public:
|
||||
{std::istreambuf_iterator<char>(&header_)}, std::istreambuf_iterator<char>()};
|
||||
JLOG(j_.trace()) << "Header: \"" << strHeader << "\"";
|
||||
|
||||
static boost::regex const kReStatus{"\\`HTTP/1\\S+ (\\d{3}) .*\\'"}; // HTTP/1.1 200 OK
|
||||
static boost::regex const kReStatus{R"(\`HTTP/1\S+ (\d{3}) .*\')"}; // HTTP/1.1 200 OK
|
||||
static boost::regex const kReSize{
|
||||
"\\`.*\\r\\nContent-Length:\\s+([0-9]+).*\\'", boost::regex::icase};
|
||||
static boost::regex const kReBody{"\\`.*\\r\\n\\r\\n(.*)\\'"};
|
||||
R"(\`.*\r\nContent-Length:\s+([0-9]+).*\')", boost::regex::icase};
|
||||
static boost::regex const kReBody{R"(\`.*\r\n\r\n(.*)\')"};
|
||||
|
||||
boost::smatch smMatch;
|
||||
// Match status code.
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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
|
||||
;
|
||||
|
||||
|
||||
@@ -84,6 +84,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',
|
||||
@@ -152,7 +153,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
|
||||
@@ -278,8 +284,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};
|
||||
}
|
||||
@@ -318,6 +327,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
|
||||
{
|
||||
|
||||
@@ -160,6 +160,14 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfTxnSignature, SoeOptional},
|
||||
{sfSigners, SoeOptional},
|
||||
});
|
||||
|
||||
add(sfSponsorSignature.jsonName.cStr(),
|
||||
sfSponsorSignature.getCode(),
|
||||
{
|
||||
{sfSigningPubKey, SoeOptional},
|
||||
{sfTxnSignature, SoeOptional},
|
||||
{sfSigners, SoeOptional},
|
||||
});
|
||||
}
|
||||
|
||||
InnerObjectFormats const&
|
||||
|
||||
@@ -15,6 +15,7 @@ LedgerFormats::getCommonFields()
|
||||
{sfLedgerIndex, SoeOptional},
|
||||
{sfLedgerEntryType, SoeRequired},
|
||||
{sfFlags, SoeRequired},
|
||||
{sfSponsor, SoeOptional},
|
||||
};
|
||||
return kCommonFields;
|
||||
}
|
||||
|
||||
@@ -87,11 +87,11 @@ sliceToHex(Slice const& slice)
|
||||
s.reserve(2 * (slice.size() + 1));
|
||||
s = "0x";
|
||||
}
|
||||
for (int i = 0; i < slice.size(); ++i)
|
||||
for (std::uint8_t const byte : slice)
|
||||
{
|
||||
static constexpr char kHex[] = "0123456789ABCDEF";
|
||||
s += kHex[((slice[i] & 0xf0) >> 4)];
|
||||
s += kHex[((slice[i] & 0x0f) >> 0)];
|
||||
s += kHex[((byte & 0xf0) >> 4)];
|
||||
s += kHex[((byte & 0x0f) >> 0)];
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -39,22 +39,30 @@ setCurrentTransactionRules(std::optional<Rules> 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
|
||||
|
||||
@@ -21,11 +21,12 @@ SOTemplate::SOTemplate(
|
||||
}
|
||||
|
||||
SOTemplate::SOTemplate(std::vector<SOElement> uniqueFields, std::vector<SOElement> 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_.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user