Compare commits

..

37 Commits

Author SHA1 Message Date
Ed Hennis
2191ef8d75 Merge branch 'develop' into ximinez/number-maxint-range 2026-03-04 17:11:35 -04:00
Ed Hennis
ea4f922492 Merge branch 'develop' into ximinez/number-maxint-range 2026-03-03 18:38:31 -04:00
Ed Hennis
9250ba9e27 Merge branch 'develop' into ximinez/number-maxint-range 2026-03-03 15:54:39 -04:00
Ed Hennis
61f38ba068 Review feedback from @copilot 2026-02-25 20:37:22 -05:00
Ed Hennis
3d5ff2c8a2 Apply suggestions from code review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-25 21:10:11 -04:00
Ed Hennis
e27249134a Merge branch 'develop' into ximinez/number-maxint-range 2026-02-24 17:34:41 -04:00
Ed Hennis
d79fdec886 Merge branch 'develop' into ximinez/number-maxint-range 2026-02-24 16:46:05 -04:00
Ed Hennis
024d05b70c Merge branch 'develop' into ximinez/number-maxint-range 2026-02-20 18:49:46 -04:00
Ed Hennis
ffb3e1da53 Merge branch 'develop' into ximinez/number-maxint-range 2026-02-20 18:26:05 -04:00
Ed Hennis
aef7e5b335 Merge branch 'develop' into ximinez/number-maxint-range 2026-02-20 17:31:47 -04:00
Ed Hennis
e2c09e79d0 Merge branch 'develop' into ximinez/number-maxint-range 2026-02-20 17:21:09 -04:00
Ed Hennis
c6f854bbd8 Merge branch 'develop' into ximinez/number-maxint-range 2026-02-20 15:14:29 -04:00
Ed Hennis
6a1e0b0f5a Merge remote-tracking branch 'upstream/develop' into ximinez/number-maxint-range
* upstream/develop:
  ci: Add dependabot config (6379)
  Fix tautological assertion (6393)
2026-02-20 13:38:46 -05:00
Ed Hennis
01f5ae0927 Merge commit '2c1fad1023' into ximinez/number-maxint-range
* commit '2c1fad1023':
  chore: Apply clang-format width 100 (6387)
2026-02-20 13:38:00 -05:00
Ed Hennis
9b4587f9af Update formatting 2026-02-20 13:29:51 -05:00
Ed Hennis
fbc6f87983 Merge commit '25cca465538a56cce501477f9e5e2c1c7ea2d84c' into ximinez/number-maxint-range
* commit '25cca465538a56cce501477f9e5e2c1c7ea2d84c':
  chore: Set clang-format width to 100 in config file (6387)
2026-02-20 13:29:06 -05:00
Ed Hennis
0871eb0cb6 Address review feedback from @Copilot
- Clarify comments and add missing header
2026-02-19 19:06:03 -05:00
Ed Hennis
2ccf132f79 Apply suggestions from code review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-19 19:02:03 -05:00
Ed Hennis
6600153958 Merge branch 'develop' into ximinez/number-maxint-range 2026-02-19 16:21:18 -05:00
Ed Hennis
fff73dac51 Merge branch 'develop' into ximinez/number-maxint-range 2026-02-18 20:18:56 -04:00
Ed Hennis
06ff77458a fixup! fixup! fixup! fixup! Address review feedback from @copilot 2026-02-05 20:33:30 -05:00
Ed Hennis
f19ecb3b80 fixup! fixup! fixup! Address review feedback from @copilot 2026-02-05 19:56:18 -05:00
Ed Hennis
cc2406bf3f fixup! fixup! Address review feedback from @copilot 2026-02-05 19:13:14 -05:00
Ed Hennis
30c65320e4 fixup! Address review feedback from @copilot 2026-02-05 18:25:23 -05:00
Ed Hennis
569d9ea94e Address review feedback from @copilot
- Update explanations.
- Use saver conversions between signed and unsigned.
2026-02-05 14:06:09 -05:00
Ed Hennis
02b7bcfa2b Merge branch 'develop' into ximinez/number-maxint-range 2026-02-05 13:29:56 -04:00
Ed Hennis
07c0c320a7 Fix formatting 2026-02-05 12:28:43 -05:00
Ed Hennis
d57e37c34b Fix renaming 2026-02-05 12:28:43 -05:00
Ed Hennis
154bb65c35 Merge remote-tracking branch 'upstream/develop' into ximinez/number-maxint-range
* upstream/develop:
  chore: Update secp256k1 and openssl (6327)
  chore: Remove unnecessary script (6326)
  refactor: Replace include guards by '#pragma once' (6322)
  chore: Remove unity builds (6300)
  refactor: Add ServiceRegistry to help modularization (6222)
  fix: Deletes expired NFToken offers from ledger (5707)
  chore: Add .zed editor config directory to .gitignore (6317)
  docs: Update API changelog, add APIv2+APIv3 version documentation (6308)
  fix: Restore config changes that broke standalone mode (6301)
  chore: Add upper-case match for ARM64 in CompilationEnv (6315)
  ci: Update hashes of XRPLF/actions (6316)
  chore: Format all cmake files without comments (6294)
  chore: Add cmake-format pre-commit hook (6279)
  chore: Remove unnecessary `boost::system` requirement from conanfile (6290)
2026-02-04 21:10:15 -05:00
Ed Hennis
111eda22e9 Merge commit '5f638f55536def0d88b970d1018a465a238e55f4' into ximinez/number-maxint-range
* commit '5f638f55536def0d88b970d1018a465a238e55f4':
  chore: Set ColumnLimit to 120 in clang-format (6288)
2026-02-04 21:09:02 -05:00
Ed Hennis
f7b6834d2a Add unit tests for normalizeToRange
- Steal changes from @pratik's #6150 to avoid UB
2026-02-04 21:08:48 -05:00
Ed Hennis
e464adaee6 Clean-ups and tweaks 2026-02-04 21:08:48 -05:00
Ed Hennis
cca92dedca Reduce expensive(?) accesses to thread_local MantissaRange 2026-02-04 21:08:48 -05:00
Ed Hennis
3d6f57a4df Fix bugs
- Simplify shiftExponent().
- Clean up to_string() to prevent integers from including "e0".
- Fix root() and root2() computations by ensuring the mantissas have
  a consistent length.
2026-02-04 21:08:46 -05:00
Ed Hennis
fc29fbe946 Convert "bool negative_ & uint64_t mantissa_" combo back to "rep mantissa_" 2026-02-04 21:08:34 -05:00
Ed Hennis
5e0a8d5c8a Remove the _ suffixes from doNormalize function parameters 2026-02-04 21:08:33 -05:00
Ed Hennis
d27788f12a Use 2^63-1 as maxMantissa for large range
- That makes minMantissa 2^63/10+1.
- Simplifies many of the existing operations, and removes the need for
  the accessors (mantissa() & exponent()) to do any math.
2026-02-04 21:08:33 -05:00
40 changed files with 1626 additions and 1627 deletions

View File

@@ -40,9 +40,11 @@ env:
NPROC_SUBTRACT: ${{ github.event.repository.private && '1' || '2' }}
jobs:
build:
publish:
runs-on: ubuntu-latest
container: ghcr.io/xrplf/ci/tools-rippled-documentation:sha-a8c7be1
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -81,23 +83,9 @@ jobs:
cmake -Donly_docs=ON ..
cmake --build . --target docs --parallel ${BUILD_NPROC}
- name: Create documentation artifact
- name: Publish documentation
if: ${{ github.event_name == 'push' }}
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0
with:
path: ${{ env.BUILD_DIR }}/docs/html
deploy:
if: ${{ github.event_name == 'push' }}
needs: build
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deploy.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deploy
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ${{ env.BUILD_DIR }}/docs/html

View File

@@ -22,19 +22,6 @@ API version 2 is available in `rippled` version 2.0.0 and later. See [API-VERSIO
This version is supported by all `rippled` versions. For WebSocket and HTTP JSON-RPC requests, it is currently the default API version used when no `api_version` is specified.
## Unreleased
This section contains changes targeting a future version.
### Additions
- `server_definitions`: Added the following new sections to the response ([#6321](https://github.com/XRPLF/rippled/pull/6321)):
- `TRANSACTION_FORMATS`: Describes the fields and their optionality for each transaction type, including common fields shared across all transactions.
- `LEDGER_ENTRY_FORMATS`: Describes the fields and their optionality for each ledger entry type, including common fields shared across all ledger entries.
- `TRANSACTION_FLAGS`: Maps transaction type names to their supported flags and flag values.
- `LEDGER_ENTRY_FLAGS`: Maps ledger entry type names to their flags and flag values.
- `ACCOUNT_SET_FLAGS`: Maps AccountSet flag names (asf flags) to their numeric values.
## XRP Ledger server version 3.1.0
[Version 3.1.0](https://github.com/XRPLF/rippled/releases/tag/3.1.0) was released on Jan 27, 2026.

52
cmake/XrplConfig.cmake Normal file
View File

@@ -0,0 +1,52 @@
include(CMakeFindDependencyMacro)
# need to represent system dependencies of the lib here
#[=========================================================[
Boost
#]=========================================================]
if (static OR APPLE OR MSVC)
set(Boost_USE_STATIC_LIBS ON)
endif ()
set(Boost_USE_MULTITHREADED ON)
if (static OR MSVC)
set(Boost_USE_STATIC_RUNTIME ON)
else ()
set(Boost_USE_STATIC_RUNTIME OFF)
endif ()
find_dependency(Boost
COMPONENTS
chrono
container
context
coroutine
date_time
filesystem
program_options
regex
system
thread)
#[=========================================================[
OpenSSL
#]=========================================================]
if (NOT DEFINED OPENSSL_ROOT_DIR)
if (DEFINED ENV{OPENSSL_ROOT})
set(OPENSSL_ROOT_DIR $ENV{OPENSSL_ROOT})
elseif (APPLE)
find_program(homebrew brew)
if (homebrew)
execute_process(COMMAND ${homebrew} --prefix openssl OUTPUT_VARIABLE OPENSSL_ROOT_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE)
endif ()
endif ()
file(TO_CMAKE_PATH "${OPENSSL_ROOT_DIR}" OPENSSL_ROOT_DIR)
endif ()
if (static OR APPLE OR MSVC)
set(OPENSSL_USE_STATIC_LIBS ON)
endif ()
set(OPENSSL_MSVC_STATIC_RT ON)
find_dependency(OpenSSL REQUIRED)
find_dependency(ZLIB)
find_dependency(date)
if (TARGET ZLIB::ZLIB)
set_target_properties(OpenSSL::Crypto PROPERTIES INTERFACE_LINK_LIBRARIES ZLIB::ZLIB)
endif ()

View File

@@ -2,28 +2,13 @@
install stuff
#]===================================================================]
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)
include(create_symbolic_link)
set(xrpl_cmakedir "${CMAKE_INSTALL_LIBDIR}/cmake/xrpl")
if(is_root_project AND TARGET xrpld)
install(TARGETS xrpld
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
COMPONENT runtime
)
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/cfg/xrpld-example.cfg"
DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}/xrpld"
RENAME xrpld.cfg
COMPONENT runtime
)
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/cfg/validators-example.txt"
DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}/xrpld"
RENAME validators.txt
COMPONENT runtime
)
endif()
# If no suffix is defined for executables (e.g. Windows uses .exe but Linux
# and macOS use none), then explicitly set it to the empty string.
if (NOT DEFINED suffix)
set(suffix "")
endif ()
install(TARGETS common
opts
@@ -49,14 +34,46 @@ install(TARGETS common
xrpl.libxrpl.server
xrpl.libxrpl.shamap
xrpl.libxrpl.tx
EXPORT xrpl_targets
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT development
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT development
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT development
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)
antithesis-sdk-cpp
EXPORT XrplExports
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES
DESTINATION include)
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/xrpl"
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
COMPONENT development
)
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
install(EXPORT XrplExports FILE XrplTargets.cmake NAMESPACE Xrpl:: DESTINATION lib/cmake/xrpl)
include(CMakePackageConfigHelpers)
write_basic_package_version_file(XrplConfigVersion.cmake VERSION ${xrpld_version}
COMPATIBILITY SameMajorVersion)
if (is_root_project AND TARGET xrpld)
install(TARGETS xrpld RUNTIME DESTINATION bin)
set_target_properties(xrpld PROPERTIES INSTALL_RPATH_USE_LINK_PATH ON)
# sample configs should not overwrite existing files
# install if-not-exists workaround as suggested by
# https://cmake.org/Bug/view.php?id=12646
install(CODE "
macro (copy_if_not_exists SRC DEST NEWNAME)
if (NOT EXISTS \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/\${DEST}/\${NEWNAME}\")
file (INSTALL FILE_PERMISSIONS OWNER_READ OWNER_WRITE DESTINATION \"\${CMAKE_INSTALL_PREFIX}/\${DEST}\" FILES \"\${SRC}\" RENAME \"\${NEWNAME}\")
else ()
message (\"-- Skipping : \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/\${DEST}/\${NEWNAME}\")
endif ()
endmacro()
copy_if_not_exists(\"${CMAKE_CURRENT_SOURCE_DIR}/cfg/xrpld-example.cfg\" etc xrpld.cfg)
copy_if_not_exists(\"${CMAKE_CURRENT_SOURCE_DIR}/cfg/validators-example.txt\" etc validators.txt)
")
install(CODE "
set(CMAKE_MODULE_PATH \"${CMAKE_MODULE_PATH}\")
include(create_symbolic_link)
create_symbolic_link(xrpld${suffix} \
\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/rippled${suffix})
")
endif ()
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/XrplConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/XrplConfigVersion.cmake DESTINATION lib/cmake/xrpl)

View File

@@ -15,6 +15,6 @@ if (validator_keys)
GIT_TAG "${current_branch}")
FetchContent_MakeAvailable(validator_keys)
set_target_properties(validator-keys PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}")
install(TARGETS validator-keys RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime)
install(TARGETS validator-keys RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
endif ()

View File

@@ -50,6 +50,5 @@ function (target_protobuf_sources target prefix)
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
# Allows #include "path/to/file.proto" used by generated files.
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${prefix}>)
install(DIRECTORY ${dir}/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
COMPONENT development FILES_MATCHING PATTERN "*.h")
install(DIRECTORY ${dir}/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} FILES_MATCHING PATTERN "*.h")
endfunction ()

View File

@@ -2,13 +2,19 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <concepts>
#include <cstdint>
#include <functional>
#include <limits>
#include <optional>
#include <ostream>
#include <stdexcept>
#include <string>
#ifdef _MSC_VER
#include <boost/multiprecision/cpp_int.hpp>
#endif // !defined(_MSC_VER)
namespace xrpl {
class Number;
@@ -16,18 +22,39 @@ class Number;
std::string
to_string(Number const& amount);
/** Returns a rough estimate of log10(value).
*
* The return value is a pair (log, rem), where log is the estimated
* base-10 logarithm (roughly floor(log10(value))), and rem is value with
* all trailing 0s removed (i.e., divided by the largest power of 10 that
* evenly divides value). If rem is 1, then value is an exact power of ten, and
* log is the exact log10(value).
*
* This function only works for positive values.
*/
template <std::unsigned_integral T>
constexpr std::pair<int, T>
logTenEstimate(T value)
{
int log = 0;
T remainder = value;
while (value >= 10)
{
if (value % 10 == 0)
remainder = remainder / 10;
value /= 10;
++log;
}
return {log, remainder};
}
template <typename T>
constexpr std::optional<int>
logTen(T value)
{
int log = 0;
while (value >= 10 && value % 10 == 0)
{
value /= 10;
++log;
}
if (value == 1)
return log;
auto const est = logTenEstimate(value);
if (est.second == 1)
return est.first;
return std::nullopt;
}
@@ -41,12 +68,10 @@ isPowerOfTen(T value)
/** MantissaRange defines a range for the mantissa of a normalized Number.
*
* The mantissa is in the range [min, max], where
* * min is a power of 10, and
* * max = min * 10 - 1.
*
* The mantissa_scale enum indicates whether the range is "small" or "large".
* This intentionally restricts the number of MantissaRanges that can be
* instantiated to two: one for each scale.
* The mantissa_scale enum indicates whether the range is "small" or
* "large". This intentionally prevents the creation of any
* MantissaRanges representing other values.
*
* The "small" scale is based on the behavior of STAmount for IOUs. It has a min
* value of 10^15, and a max value of 10^16-1. This was sufficient for
@@ -60,8 +85,8 @@ isPowerOfTen(T value)
* "large" scale.
*
* The "large" scale is intended to represent all values that can be represented
* by an STAmount - IOUs, XRP, and MPTs. It has a min value of 10^18, and a max
* value of 10^19-1.
* by an STAmount - IOUs, XRP, and MPTs. It has a min value of 2^63/10+1
* (truncated), and a max value of 2^63-1.
*
* Note that if the mentioned amendments are eventually retired, this class
* should be left in place, but the "small" scale option should be removed. This
@@ -73,25 +98,56 @@ struct MantissaRange
enum mantissa_scale { small, large };
explicit constexpr MantissaRange(mantissa_scale scale_)
: min(getMin(scale_)), max(min * 10 - 1), log(logTen(min).value_or(-1)), scale(scale_)
: max(getMax(scale_))
, min(computeMin(max))
, internalMin(getInternalMin(scale_, min))
, log(computeLog(min))
, scale(scale_)
{
// Keep the error messages terse. Since this is constexpr, if any of these throw, it won't
// compile, so there's no real need to worry about runtime exceptions here.
if (min * 10 <= max)
throw std::out_of_range("Invalid mantissa range: min * 10 <= max");
if (max / 10 >= min)
throw std::out_of_range("Invalid mantissa range: max / 10 >= min");
if ((min - 1) * 10 > max)
throw std::out_of_range("Invalid mantissa range: (min - 1) * 10 > max");
// This is a little hacky
if ((max + 10) / 10 < min)
throw std::out_of_range("Invalid mantissa range: (max + 10) / 10 < min");
if (computeLog(internalMin) != log)
throw std::out_of_range("Invalid mantissa range: computeLog(internalMin) != log");
}
rep min;
// Explicitly delete copy and move operations
MantissaRange(MantissaRange const&) = delete;
MantissaRange(MantissaRange&&) = delete;
MantissaRange&
operator=(MantissaRange const&) = delete;
MantissaRange&
operator=(MantissaRange&&) = delete;
rep max;
rep min;
/* Used to determine if mantissas are in range, but have fewer digits than max.
*
* Unlike min, internalMin is always an exact power of 10, so a mantissa in the internal
* representation will always have a consistent number of digits.
*/
rep internalMin;
int log;
mantissa_scale scale;
private:
static constexpr rep
getMin(mantissa_scale scale_)
getMax(mantissa_scale scale)
{
switch (scale_)
switch (scale)
{
case small:
return 1'000'000'000'000'000ULL;
return 9'999'999'999'999'999ULL;
case large:
return 1'000'000'000'000'000'000ULL;
return std::numeric_limits<std::int64_t>::max();
default:
// Since this can never be called outside a non-constexpr
// context, this throw assures that the build fails if an
@@ -99,19 +155,59 @@ private:
throw std::runtime_error("Unknown mantissa scale");
}
}
static constexpr rep
computeMin(rep max)
{
return max / 10 + 1;
}
static constexpr rep
getInternalMin(mantissa_scale scale, rep min)
{
switch (scale)
{
case large:
return 1'000'000'000'000'000'000ULL;
default:
if (isPowerOfTen(min))
return min;
throw std::runtime_error("Unknown/bad mantissa scale");
}
}
static constexpr rep
computeLog(rep min)
{
auto const estimate = logTenEstimate(min);
return estimate.first + (estimate.second == 1 ? 0 : 1);
}
};
// Like std::integral, but only 64-bit integral types.
template <class T>
concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::uint64_t>;
namespace detail {
#ifdef _MSC_VER
using uint128_t = boost::multiprecision::uint128_t;
using int128_t = boost::multiprecision::int128_t;
#else // !defined(_MSC_VER)
using uint128_t = __uint128_t;
using int128_t = __int128_t;
#endif // !defined(_MSC_VER)
template <class T>
concept UnsignedMantissa = std::is_unsigned_v<T> || std::is_same_v<T, uint128_t>;
} // namespace detail
/** Number is a floating point type that can represent a wide range of values.
*
* It can represent all values that can be represented by an STAmount -
* regardless of asset type - XRPAmount, MPTAmount, and IOUAmount, with at least
* as much precision as those types require.
*
* ---- Internal Representation ----
* ---- Internal Operational Representation ----
*
* Internally, Number is represented with three values:
* 1. a bool sign flag,
@@ -126,15 +222,21 @@ concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::u
*
* A non-zero mantissa is (almost) always normalized, meaning it and the
* exponent are grown or shrunk until the mantissa is in the range
* [MantissaRange.min, MantissaRange.max].
* [MantissaRange.internalMin, MantissaRange.internalMin * 10 - 1].
*
* This internal representation is only used during some operations to ensure
* that the mantissa is a known, predictable size. The class itself stores the
* values using the external representation described below.
*
* Note:
* 1. Normalization can be disabled by using the "unchecked" ctor tag. This
* should only be used at specific conversion points, some constexpr
* values, and in unit tests.
* 2. The max of the "large" range, 10^19-1, is the largest 10^X-1 value that
* fits in an unsigned 64-bit number. (10^19-1 < 2^64-1 and
* 10^20-1 > 2^64-1). This avoids under- and overflows.
* 2. Unlike MantissaRange.min, internalMin is always an exact power of 10,
* so a mantissa in the internal representation will always have a
* consistent number of digits.
* 3. The functions toInternal() and fromInternal() are used to convert
* between the two representations.
*
* ---- External Interface ----
*
@@ -147,13 +249,12 @@ concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::u
* represent the full range of valid XRP and MPT integer values accurately.
*
* Note:
* 1. 2^63-1 is between 10^18 and 10^19-1, which are the limits of the "large"
* mantissa range.
* 1. The "large" mantissa range is (2^63/10+1) to 2^63-1. 2^63-1 is between
* 10^18 and 10^19-1, and (2^63/10+1) is between 10^17 and 10^18-1. Thus,
* the mantissa may have 18 or 19 digits. This value will be modified to
* always have 19 digits before some operations to ensure consistency.
* 2. The functions mantissa() and exponent() return the external view of the
* Number value, specifically using a signed 63-bit mantissa. This may
* require altering the internal representation to fit into that range
* before the value is returned. The interface guarantees consistency of
* the two values.
* Number value, specifically using a signed 63-bit mantissa.
* 3. Number cannot represent -2^63 (std::numeric_limits<std::int64_t>::min())
* as an exact integer, but it doesn't need to, because all asset values
* on-ledger are non-negative. This is due to implementation details of
@@ -208,8 +309,7 @@ class Number
using rep = std::int64_t;
using internalrep = MantissaRange::rep;
bool negative_{false};
internalrep mantissa_{0};
rep mantissa_{0};
int exponent_{std::numeric_limits<int>::lowest()};
public:
@@ -217,10 +317,6 @@ public:
constexpr static int minExponent = -32768;
constexpr static int maxExponent = 32768;
constexpr static internalrep maxRep = std::numeric_limits<rep>::max();
static_assert(maxRep == 9'223'372'036'854'775'807);
static_assert(-maxRep == std::numeric_limits<rep>::min() + 1);
// May need to make unchecked private
struct unchecked
{
@@ -298,8 +394,7 @@ public:
friend constexpr bool
operator==(Number const& x, Number const& y) noexcept
{
return x.negative_ == y.negative_ && x.mantissa_ == y.mantissa_ &&
x.exponent_ == y.exponent_;
return x.mantissa_ == y.mantissa_ && x.exponent_ == y.exponent_;
}
friend constexpr bool
@@ -313,8 +408,8 @@ public:
{
// If the two amounts have different signs (zero is treated as positive)
// then the comparison is true iff the left is negative.
bool const lneg = x.negative_;
bool const rneg = y.negative_;
bool const lneg = x.mantissa_ < 0;
bool const rneg = y.mantissa_ < 0;
if (lneg != rneg)
return lneg;
@@ -342,7 +437,7 @@ public:
constexpr int
signum() const noexcept
{
return negative_ ? -1 : (mantissa_ ? 1 : 0);
return mantissa_ < 0 ? -1 : (mantissa_ ? 1 : 0);
}
Number
@@ -381,6 +476,9 @@ public:
friend Number
root2(Number f);
friend Number
power(Number const& f, unsigned n, unsigned d);
// Thread local rounding control. Default is to_nearest
enum rounding_mode { to_nearest, towards_zero, downward, upward };
static rounding_mode
@@ -445,22 +543,39 @@ private:
static_assert(isPowerOfTen(smallRange.min));
static_assert(smallRange.min == 1'000'000'000'000'000LL);
static_assert(smallRange.max == 9'999'999'999'999'999LL);
static_assert(smallRange.internalMin == smallRange.min);
static_assert(smallRange.log == 15);
static_assert(smallRange.min < maxRep);
static_assert(smallRange.max < maxRep);
constexpr static MantissaRange largeRange{MantissaRange::large};
static_assert(isPowerOfTen(largeRange.min));
static_assert(largeRange.min == 1'000'000'000'000'000'000ULL);
static_assert(largeRange.max == internalrep(9'999'999'999'999'999'999ULL));
static_assert(!isPowerOfTen(largeRange.min));
static_assert(largeRange.min == 922'337'203'685'477'581ULL);
static_assert(largeRange.max == internalrep(9'223'372'036'854'775'807ULL));
static_assert(largeRange.max == std::numeric_limits<rep>::max());
static_assert(largeRange.internalMin == 1'000'000'000'000'000'000ULL);
static_assert(largeRange.log == 18);
static_assert(largeRange.min < maxRep);
static_assert(largeRange.max > maxRep);
// There are 2 values that will not fit in largeRange without some extra
// work
// * 9223372036854775808
// * 9223372036854775809
// They both end up < min, but with a leftover. If they round up, everything
// will be fine. If they don't, we'll need to bring them up into range.
// Guard::bringIntoRange handles this situation.
// The range for the mantissa when normalized.
// Use reference_wrapper to avoid making copies, and prevent accidentally
// changing the values inside the range.
static thread_local std::reference_wrapper<MantissaRange const> range_;
// And one is needed because it needs to choose between oneSmall and
// oneLarge based on the current range
static Number
one(MantissaRange const& range);
static Number
root(MantissaRange const& range, Number f, unsigned d);
void
normalize(MantissaRange const& range);
void
normalize();
@@ -483,11 +598,14 @@ private:
friend void
doNormalize(
bool& negative,
T& mantissa_,
int& exponent_,
T& mantissa,
int& exponent,
MantissaRange::rep const& minMantissa,
MantissaRange::rep const& maxMantissa);
bool
isnormal(MantissaRange const& range) const noexcept;
bool
isnormal() const noexcept;
@@ -497,14 +615,60 @@ private:
Number
shiftExponent(int exponentDelta) const;
// Safely convert rep (int64) mantissa to internalrep (uint64). If the rep
// is negative, returns the positive value. This takes a little extra work
// because converting std::numeric_limits<std::int64_t>::min() flirts with
// UB, and can vary across compilers.
// Safely return the absolute value of a rep (int64) mantissa as an internalrep (uint64).
static internalrep
externalToInternal(rep mantissa);
/** Breaks down the number into components, potentially de-normalizing it.
*
* Ensures that the mantissa always has range_.log + 1 digits.
*
*/
template <detail::UnsignedMantissa Rep = internalrep>
std::tuple<bool, Rep, int>
toInternal(MantissaRange const& range) const;
/** Breaks down the number into components, potentially de-normalizing it.
*
* Ensures that the mantissa always has range_.log + 1 digits.
*
*/
template <detail::UnsignedMantissa Rep = internalrep>
std::tuple<bool, Rep, int>
toInternal() const;
/** Rebuilds the number from components.
*
* If "expectNormal" is true, the values are expected to be normalized - all
* in their valid ranges.
*
* If "expectNormal" is false, the values are expected to be "near
* normalized", meaning that the mantissa has to be modified at most once to
* bring it back into range.
*
*/
template <bool expectNormal = true, detail::UnsignedMantissa Rep = internalrep>
void
fromInternal(bool negative, Rep mantissa, int exponent, MantissaRange const* pRange);
/** Rebuilds the number from components.
*
* If "expectNormal" is true, the values are expected to be normalized - all
* in their valid ranges.
*
* If "expectNormal" is false, the values are expected to be "near
* normalized", meaning that the mantissa has to be modified at most once to
* bring it back into range.
*
*/
template <bool expectNormal = true, detail::UnsignedMantissa Rep = internalrep>
void
fromInternal(bool negative, Rep mantissa, int exponent);
class Guard;
public:
constexpr static internalrep largestMantissa = largeRange.max;
};
inline constexpr Number::Number(
@@ -512,7 +676,8 @@ inline constexpr Number::Number(
internalrep mantissa,
int exponent,
unchecked) noexcept
: negative_(negative), mantissa_{mantissa}, exponent_{exponent}
: mantissa_{negative ? -static_cast<rep>(mantissa) : static_cast<rep>(mantissa)}
, exponent_{exponent}
{
}
@@ -523,12 +688,6 @@ inline constexpr Number::Number(internalrep mantissa, int exponent, unchecked) n
constexpr static Number numZero{};
inline Number::Number(bool negative, internalrep mantissa, int exponent, normalized)
: Number(negative, mantissa, exponent, unchecked{})
{
normalize();
}
inline Number::Number(internalrep mantissa, int exponent, normalized)
: Number(false, mantissa, exponent, normalized{})
{
@@ -551,17 +710,7 @@ inline Number::Number(rep mantissa) : Number{mantissa, 0}
inline constexpr Number::rep
Number::mantissa() const noexcept
{
auto m = mantissa_;
if (m > maxRep)
{
XRPL_ASSERT_PARTS(
!isnormal() || (m % 10 == 0 && m / 10 <= maxRep),
"xrpl::Number::mantissa",
"large normalized mantissa has no remainder");
m /= 10;
}
auto const sign = negative_ ? -1 : 1;
return sign * static_cast<Number::rep>(m);
return mantissa_;
}
/** Returns the exponent of the external view of the Number.
@@ -572,16 +721,7 @@ Number::mantissa() const noexcept
inline constexpr int
Number::exponent() const noexcept
{
auto e = exponent_;
if (mantissa_ > maxRep)
{
XRPL_ASSERT_PARTS(
!isnormal() || (mantissa_ % 10 == 0 && mantissa_ / 10 <= maxRep),
"xrpl::Number::exponent",
"large normalized mantissa has no remainder");
++e;
}
return e;
return exponent_;
}
inline constexpr Number
@@ -596,7 +736,7 @@ Number::operator-() const noexcept
if (mantissa_ == 0)
return Number{};
auto x = *this;
x.negative_ = !x.negative_;
x.mantissa_ = -x.mantissa_;
return x;
}
@@ -677,42 +817,58 @@ Number::min() noexcept
inline Number
Number::max() noexcept
{
return Number{false, std::min(range_.get().max, maxRep), maxExponent, unchecked{}};
return Number{false, range_.get().max, maxExponent, unchecked{}};
}
inline Number
Number::lowest() noexcept
{
return Number{true, std::min(range_.get().max, maxRep), maxExponent, unchecked{}};
return Number{true, range_.get().max, maxExponent, unchecked{}};
}
inline bool
Number::isnormal(MantissaRange const& range) const noexcept
{
auto const abs_m = externalToInternal(mantissa_);
return *this == Number{} ||
(range.min <= abs_m && abs_m <= range.max && //
minExponent <= exponent_ && exponent_ <= maxExponent);
}
inline bool
Number::isnormal() const noexcept
{
MantissaRange const& range = range_;
auto const abs_m = mantissa_;
return *this == Number{} ||
(range.min <= abs_m && abs_m <= range.max && (abs_m <= maxRep || abs_m % 10 == 0) &&
minExponent <= exponent_ && exponent_ <= maxExponent);
return isnormal(range_);
}
template <Integral64 T>
std::pair<T, int>
Number::normalizeToRange(T minMantissa, T maxMantissa) const
{
bool negative = negative_;
internalrep mantissa = mantissa_;
bool negative = mantissa_ < 0;
internalrep mantissa = externalToInternal(mantissa_);
int exponent = exponent_;
if constexpr (std::is_unsigned_v<T>)
{
XRPL_ASSERT_PARTS(
!negative,
"xrpl::Number::normalizeToRange",
"Number is non-negative for unsigned range.");
// To avoid logical errors in release builds, throw if the Number is
// negative for an unsigned range.
if (negative)
throw std::runtime_error(
"Number::normalizeToRange: Number is negative for "
"unsigned range.");
}
Number::normalize(negative, mantissa, exponent, minMantissa, maxMantissa);
auto const sign = negative ? -1 : 1;
return std::make_pair(static_cast<T>(sign * mantissa), exponent);
// Cast mantissa to signed type first (if T is a signed type) to avoid
// unsigned integer overflow when multiplying by negative sign
T signedMantissa = negative ? -static_cast<T>(mantissa) : static_cast<T>(mantissa);
return std::make_pair(signedMantissa, exponent);
}
inline constexpr Number

View File

@@ -30,11 +30,9 @@ public:
Item(
char const* name,
KeyType type,
std::vector<SOElement> uniqueFields,
std::vector<SOElement> commonFields)
: soTemplate_(std::move(uniqueFields), std::move(commonFields))
, name_(name)
, type_(type)
std::initializer_list<SOElement> uniqueFields,
std::initializer_list<SOElement> commonFields)
: soTemplate_(uniqueFields, commonFields), name_(name), type_(type)
{
// Verify that KeyType is appropriate.
static_assert(
@@ -144,16 +142,16 @@ protected:
@param name The name of this format.
@param type The type of this format.
@param uniqueFields A std::vector of unique fields
@param commonFields A std::vector of common fields
@param uniqueFields An std::initializer_list of unique fields
@param commonFields An std::initializer_list of common fields
@return The created format.
*/
Item const&
add(char const* name,
KeyType type,
std::vector<SOElement> uniqueFields,
std::vector<SOElement> commonFields = {})
std::initializer_list<SOElement> uniqueFields,
std::initializer_list<SOElement> commonFields = {})
{
if (auto const item = findByType(type))
{
@@ -162,7 +160,7 @@ protected:
item->getName());
}
formats_.emplace_front(name, type, std::move(uniqueFields), std::move(commonFields));
formats_.emplace_front(name, type, uniqueFields, commonFields);
Item const& item{formats_.front()};
names_[name] = &item;

View File

@@ -2,34 +2,36 @@
#include <xrpl/protocol/KnownFormats.h>
#include <map>
#include <string>
#include <vector>
namespace xrpl {
/** Identifiers for on-ledger objects.
Each ledger object requires a unique type identifier, which is stored within the object itself;
this makes it possible to iterate the entire ledger and determine each object's type and verify
that the object you retrieved from a given hash matches the expected type.
Each ledger object requires a unique type identifier, which is stored
within the object itself; this makes it possible to iterate the entire
ledger and determine each object's type and verify that the object you
retrieved from a given hash matches the expected type.
@warning Since these values are stored inside objects stored on the ledger they are part of the
protocol.
**Changing them should be avoided because without special handling, this will result in a hard
@warning Since these values are stored inside objects stored on the ledger
they are part of the protocol. **Changing them should be avoided
because without special handling, this will result in a hard
fork.**
@note Values outside this range may be used internally by the code for various purposes, but
attempting to use such values to identify on-ledger objects will result in an invariant failure.
@note Values outside this range may be used internally by the code for
various purposes, but attempting to use such values to identify
on-ledger objects will results in an invariant failure.
@note When retiring types, the specific values should not be removed but should be marked as
[[deprecated]]. This is to avoid accidental reuse of identifiers.
@note When retiring types, the specific values should not be removed but
should be marked as [[deprecated]]. This is to avoid accidental
reuse of identifiers.
@todo The C++ language does not enable checking for duplicate values here.
If it becomes possible then we should do this.
@todo The C++ language does not enable checking for duplicate values
here. If it becomes possible then we should do this.
@ingroup protocol
*/
enum LedgerEntryType : std::uint16_t {
// clang-format off
enum LedgerEntryType : std::uint16_t
{
#pragma push_macro("LEDGER_ENTRY")
#undef LEDGER_ENTRY
@@ -44,10 +46,12 @@ enum LedgerEntryType : std::uint16_t {
//---------------------------------------------------------------------------
/** A special type, matching any ledger entry type.
The value does not represent a concrete type, but rather is used in contexts where the
specific type of a ledger object is unimportant, unknown or unavailable.
The value does not represent a concrete type, but rather is used in
contexts where the specific type of a ledger object is unimportant,
unknown or unavailable.
Objects with this special type cannot be created or stored on the ledger.
Objects with this special type cannot be created or stored on the
ledger.
\sa keylet::unchecked
*/
@@ -55,11 +59,12 @@ enum LedgerEntryType : std::uint16_t {
/** A special type, matching any ledger type except directory nodes.
The value does not represent a concrete type, but rather is used in contexts where the
ledger object must not be a directory node but its specific type is otherwise unimportant,
unknown or unavailable.
The value does not represent a concrete type, but rather is used in
contexts where the ledger object must not be a directory node but
its specific type is otherwise unimportant, unknown or unavailable.
Objects with this special type cannot be created or stored on the ledger.
Objects with this special type cannot be created or stored on the
ledger.
\sa keylet::child
*/
@@ -88,188 +93,104 @@ enum LedgerEntryType : std::uint16_t {
Support for this type of object was never implemented.
No objects of this type were ever created.
*/
ltGENERATOR_MAP [[deprecated("This object type is not supported and should not be used.")]] =
0x0067,
ltGENERATOR_MAP [[deprecated("This object type is not supported and should not be used.")]] = 0x0067,
};
/** Ledger object flags.
These flags are specified in ledger objects and modify their behavior.
@warning Ledger object flags form part of the protocol.
**Changing them should be avoided because without special handling, this will result in a hard
fork.**
@ingroup protocol
*/
#pragma push_macro("XMACRO")
#pragma push_macro("TO_VALUE")
#pragma push_macro("VALUE_TO_MAP")
#pragma push_macro("NULL_NAME")
#pragma push_macro("TO_MAP")
#pragma push_macro("ALL_LEDGER_FLAGS")
#undef XMACRO
#undef TO_VALUE
#undef VALUE_TO_MAP
#undef NULL_NAME
#undef TO_MAP
#undef ALL_LEDGER_FLAGS
// clang-format off
#define XMACRO(LEDGER_OBJECT, LSF_FLAG, LSF_FLAG2) \
LEDGER_OBJECT(AccountRoot, \
LSF_FLAG(lsfPasswordSpent, 0x00010000) /* True, if password set fee is spent. */ \
LSF_FLAG(lsfRequireDestTag, 0x00020000) /* True, to require a DestinationTag for payments. */ \
LSF_FLAG(lsfRequireAuth, 0x00040000) /* True, to require a authorization to hold IOUs. */ \
LSF_FLAG(lsfDisallowXRP, 0x00080000) /* True, to disallow sending XRP. */ \
LSF_FLAG(lsfDisableMaster, 0x00100000) /* True, force regular key */ \
LSF_FLAG(lsfNoFreeze, 0x00200000) /* True, cannot freeze ripple states */ \
LSF_FLAG(lsfGlobalFreeze, 0x00400000) /* True, all assets frozen */ \
LSF_FLAG(lsfDefaultRipple, 0x00800000) /* True, incoming trust lines allow rippling by default */ \
LSF_FLAG(lsfDepositAuth, 0x01000000) /* True, all deposits require authorization */ \
LSF_FLAG(lsfDisallowIncomingNFTokenOffer, 0x04000000) /* True, reject new incoming NFT offers */ \
LSF_FLAG(lsfDisallowIncomingCheck, 0x08000000) /* True, reject new checks */ \
LSF_FLAG(lsfDisallowIncomingPayChan, 0x10000000) /* True, reject new paychans */ \
LSF_FLAG(lsfDisallowIncomingTrustline, 0x20000000) /* True, reject new trustlines (only if no issued assets) */ \
LSF_FLAG(lsfAllowTrustLineLocking, 0x40000000) /* True, enable trustline locking */ \
LSF_FLAG(lsfAllowTrustLineClawback, 0x80000000)) /* True, enable clawback */ \
\
LEDGER_OBJECT(Offer, \
LSF_FLAG(lsfPassive, 0x00010000) \
LSF_FLAG(lsfSell, 0x00020000) /* True, offer was placed as a sell. */ \
LSF_FLAG(lsfHybrid, 0x00040000)) /* True, offer is hybrid. */ \
\
LEDGER_OBJECT(RippleState, \
LSF_FLAG(lsfLowReserve, 0x00010000) /* True, if entry counts toward reserve. */ \
LSF_FLAG(lsfHighReserve, 0x00020000) \
LSF_FLAG(lsfLowAuth, 0x00040000) \
LSF_FLAG(lsfHighAuth, 0x00080000) \
LSF_FLAG(lsfLowNoRipple, 0x00100000) \
LSF_FLAG(lsfHighNoRipple, 0x00200000) \
LSF_FLAG(lsfLowFreeze, 0x00400000) /* True, low side has set freeze flag */ \
LSF_FLAG(lsfHighFreeze, 0x00800000) /* True, high side has set freeze flag */ \
LSF_FLAG(lsfAMMNode, 0x01000000) /* True, trust line to AMM. */ \
/* Used by client apps to identify payments via AMM. */ \
LSF_FLAG(lsfLowDeepFreeze, 0x02000000) /* True, low side has set deep freeze flag */ \
LSF_FLAG(lsfHighDeepFreeze, 0x04000000)) /* True, high side has set deep freeze flag */ \
\
LEDGER_OBJECT(SignerList, \
LSF_FLAG(lsfOneOwnerCount, 0x00010000)) /* True, uses only one OwnerCount */ \
\
LEDGER_OBJECT(DirNode, \
LSF_FLAG(lsfNFTokenBuyOffers, 0x00000001) \
LSF_FLAG(lsfNFTokenSellOffers, 0x00000002)) \
\
LEDGER_OBJECT(NFTokenOffer, \
LSF_FLAG(lsfSellNFToken, 0x00000001)) \
\
LEDGER_OBJECT(MPTokenIssuance, \
LSF_FLAG(lsfMPTLocked, 0x00000001) /* Also used in ltMPTOKEN */ \
LSF_FLAG(lsfMPTCanLock, 0x00000002) \
LSF_FLAG(lsfMPTRequireAuth, 0x00000004) \
LSF_FLAG(lsfMPTCanEscrow, 0x00000008) \
LSF_FLAG(lsfMPTCanTrade, 0x00000010) \
LSF_FLAG(lsfMPTCanTransfer, 0x00000020) \
LSF_FLAG(lsfMPTCanClawback, 0x00000040)) \
\
LEDGER_OBJECT(MPTokenIssuanceMutable, \
LSF_FLAG(lsmfMPTCanMutateCanLock, 0x00000002) \
LSF_FLAG(lsmfMPTCanMutateRequireAuth, 0x00000004) \
LSF_FLAG(lsmfMPTCanMutateCanEscrow, 0x00000008) \
LSF_FLAG(lsmfMPTCanMutateCanTrade, 0x00000010) \
LSF_FLAG(lsmfMPTCanMutateCanTransfer, 0x00000020) \
LSF_FLAG(lsmfMPTCanMutateCanClawback, 0x00000040) \
LSF_FLAG(lsmfMPTCanMutateMetadata, 0x00010000) \
LSF_FLAG(lsmfMPTCanMutateTransferFee, 0x00020000)) \
\
LEDGER_OBJECT(MPToken, \
LSF_FLAG2(lsfMPTLocked, 0x00000001) \
LSF_FLAG(lsfMPTAuthorized, 0x00000002)) \
\
LEDGER_OBJECT(Credential, \
LSF_FLAG(lsfAccepted, 0x00010000)) \
\
LEDGER_OBJECT(Vault, \
LSF_FLAG(lsfVaultPrivate, 0x00010000)) \
\
LEDGER_OBJECT(Loan, \
LSF_FLAG(lsfLoanDefault, 0x00010000) \
LSF_FLAG(lsfLoanImpaired, 0x00020000) \
LSF_FLAG(lsfLoanOverpayment, 0x00040000)) /* True, loan allows overpayments */
/**
@ingroup protocol
*/
enum LedgerSpecificFlags {
// ltACCOUNT_ROOT
lsfPasswordSpent = 0x00010000, // True, if password set fee is spent.
lsfRequireDestTag =
0x00020000, // True, to require a DestinationTag for payments.
lsfRequireAuth =
0x00040000, // True, to require a authorization to hold IOUs.
lsfDisallowXRP = 0x00080000, // True, to disallow sending XRP.
lsfDisableMaster = 0x00100000, // True, force regular key
lsfNoFreeze = 0x00200000, // True, cannot freeze ripple states
lsfGlobalFreeze = 0x00400000, // True, all assets frozen
lsfDefaultRipple =
0x00800000, // True, incoming trust lines allow rippling by default
lsfDepositAuth = 0x01000000, // True, all deposits require authorization
/* // reserved for Hooks amendment
lsfTshCollect = 0x02000000, // True, allow TSH collect-calls to acc hooks
*/
lsfDisallowIncomingNFTokenOffer =
0x04000000, // True, reject new incoming NFT offers
lsfDisallowIncomingCheck =
0x08000000, // True, reject new checks
lsfDisallowIncomingPayChan =
0x10000000, // True, reject new paychans
lsfDisallowIncomingTrustline =
0x20000000, // True, reject new trustlines (only if no issued assets)
lsfAllowTrustLineLocking =
0x40000000, // True, enable trustline locking
lsfAllowTrustLineClawback =
0x80000000, // True, enable clawback
// clang-format on
// ltOFFER
lsfPassive = 0x00010000,
lsfSell = 0x00020000, // True, offer was placed as a sell.
lsfHybrid = 0x00040000, // True, offer is hybrid.
// Create all the flag values as an enum.
//
// example:
// enum LedgerSpecificFlags {
// lsfPasswordSpent = 0x00010000,
// lsfRequireDestTag = 0x00020000,
// ...
// };
#define TO_VALUE(name, value) name = value,
#define NULL_NAME(name, values) values
#define NULL_OUTPUT(name, value)
enum LedgerSpecificFlags : std::uint32_t { XMACRO(NULL_NAME, TO_VALUE, NULL_OUTPUT) };
// ltRIPPLE_STATE
lsfLowReserve = 0x00010000, // True, if entry counts toward reserve.
lsfHighReserve = 0x00020000,
lsfLowAuth = 0x00040000,
lsfHighAuth = 0x00080000,
lsfLowNoRipple = 0x00100000,
lsfHighNoRipple = 0x00200000,
lsfLowFreeze = 0x00400000, // True, low side has set freeze flag
lsfHighFreeze = 0x00800000, // True, high side has set freeze flag
lsfLowDeepFreeze = 0x02000000, // True, low side has set deep freeze flag
lsfHighDeepFreeze = 0x04000000, // True, high side has set deep freeze flag
lsfAMMNode = 0x01000000, // True, trust line to AMM. Used by client
// apps to identify payments via AMM.
// Create getter functions for each set of flags using Meyer's singleton pattern.
// This avoids static initialization order fiasco while still providing efficient access.
// This is used below in `getAllLedgerFlags()` to generate the server_definitions RPC output.
//
// example:
// inline LedgerFlagMap const& getAccountRootFlags() {
// static LedgerFlagMap const flags = {
// {"lsfPasswordSpent", 0x00010000},
// {"lsfRequireDestTag", 0x00020000},
// ...};
// return flags;
// }
using LedgerFlagMap = std::map<std::string, std::uint32_t>;
#define VALUE_TO_MAP(name, value) {#name, value},
#define TO_MAP(name, values) \
inline LedgerFlagMap const& get##name##Flags() \
{ \
static LedgerFlagMap const flags = {values}; \
return flags; \
}
XMACRO(TO_MAP, VALUE_TO_MAP, VALUE_TO_MAP)
// ltSIGNER_LIST
lsfOneOwnerCount = 0x00010000, // True, uses only one OwnerCount
// Create a getter function for all ledger flag maps using Meyer's singleton pattern.
// This is used to generate the server_definitions RPC output.
//
// example:
// inline std::vector<std::pair<std::string, LedgerFlagMap>> const& getAllLedgerFlags() {
// static std::vector<std::pair<std::string, LedgerFlagMap>> const flags = {
// {"AccountRoot", getAccountRootFlags()},
// ...};
// return flags;
// }
#define ALL_LEDGER_FLAGS(name, values) {#name, get##name##Flags()},
inline std::vector<std::pair<std::string, LedgerFlagMap>> const&
getAllLedgerFlags()
{
static std::vector<std::pair<std::string, LedgerFlagMap>> const flags = {
XMACRO(ALL_LEDGER_FLAGS, NULL_OUTPUT, NULL_OUTPUT)};
return flags;
}
// ltDIR_NODE
lsfNFTokenBuyOffers = 0x00000001,
lsfNFTokenSellOffers = 0x00000002,
#undef XMACRO
#undef TO_VALUE
#undef VALUE_TO_MAP
#undef NULL_NAME
#undef NULL_OUTPUT
#undef TO_MAP
#undef ALL_LEDGER_FLAGS
// ltNFTOKEN_OFFER
lsfSellNFToken = 0x00000001,
#pragma pop_macro("XMACRO")
#pragma pop_macro("TO_VALUE")
#pragma pop_macro("VALUE_TO_MAP")
#pragma pop_macro("NULL_NAME")
#pragma pop_macro("TO_MAP")
#pragma pop_macro("ALL_LEDGER_FLAGS")
// ltMPTOKEN_ISSUANCE
lsfMPTLocked = 0x00000001, // Also used in ltMPTOKEN
lsfMPTCanLock = 0x00000002,
lsfMPTRequireAuth = 0x00000004,
lsfMPTCanEscrow = 0x00000008,
lsfMPTCanTrade = 0x00000010,
lsfMPTCanTransfer = 0x00000020,
lsfMPTCanClawback = 0x00000040,
lsmfMPTCanMutateCanLock = 0x00000002,
lsmfMPTCanMutateRequireAuth = 0x00000004,
lsmfMPTCanMutateCanEscrow = 0x00000008,
lsmfMPTCanMutateCanTrade = 0x00000010,
lsmfMPTCanMutateCanTransfer = 0x00000020,
lsmfMPTCanMutateCanClawback = 0x00000040,
lsmfMPTCanMutateMetadata = 0x00010000,
lsmfMPTCanMutateTransferFee = 0x00020000,
// ltMPTOKEN
lsfMPTAuthorized = 0x00000002,
// ltCREDENTIAL
lsfAccepted = 0x00010000,
// ltVAULT
lsfVaultPrivate = 0x00010000,
// ltLOAN
lsfLoanDefault = 0x00010000,
lsfLoanImpaired = 0x00020000,
lsfLoanOverpayment = 0x00040000, // True, loan allows overpayments
};
//------------------------------------------------------------------------------
@@ -286,10 +207,6 @@ private:
public:
static LedgerFormats const&
getInstance();
// Fields shared by all ledger entry formats:
static std::vector<SOElement> const&
getCommonFields();
};
} // namespace xrpl

View File

@@ -232,7 +232,7 @@ std::size_t constexpr maxMPTokenMetadataLength = 1024;
/** The maximum amount of MPTokenIssuance */
std::uint64_t constexpr maxMPTokenAmount = 0x7FFF'FFFF'FFFF'FFFFull;
static_assert(Number::maxRep >= maxMPTokenAmount);
static_assert(Number::largestMantissa >= maxMPTokenAmount);
/** The maximum length of Data payload */
std::size_t constexpr maxDataPayloadLength = 256;

View File

@@ -6,7 +6,6 @@
#include <functional>
#include <initializer_list>
#include <stdexcept>
#include <vector>
namespace xrpl {
@@ -98,12 +97,8 @@ public:
operator=(SOTemplate&& other) = default;
/** Create a template populated with all fields.
After creating the template fields cannot be added, modified, or removed.
*/
SOTemplate(std::vector<SOElement> uniqueFields, std::vector<SOElement> commonFields = {});
/** Create a template populated with all fields.
Note: Defers to the vector constructor above.
After creating the template fields cannot be
added, modified, or removed.
*/
SOTemplate(
std::initializer_list<SOElement> uniqueFields,

View File

@@ -539,6 +539,8 @@ STAmount::fromNumber(A const& a, Number const& number)
return STAmount{asset, intValue, 0, negative};
}
XRPL_ASSERT_PARTS(
working.signum() >= 0, "xrpl::STAmount::fromNumber", "non-negative Number to normalize");
auto const [mantissa, exponent] = working.normalizeToRange(cMinValue, cMaxValue);
return STAmount{asset, mantissa, exponent, negative};

View File

@@ -23,7 +23,7 @@ systemName()
/** Number of drops in the genesis account. */
constexpr XRPAmount INITIAL_XRP{100'000'000'000 * DROPS_PER_XRP};
static_assert(INITIAL_XRP.drops() == 100'000'000'000'000'000);
static_assert(Number::maxRep >= INITIAL_XRP.drops());
static_assert(Number::largestMantissa >= INITIAL_XRP.drops());
/** Returns true if the amount does not exceed the initial XRP in existence. */
inline bool

View File

@@ -3,444 +3,294 @@
#include <xrpl/protocol/LedgerFormats.h>
#include <cstdint>
#include <map>
#include <string>
#include <utility>
#include <vector>
namespace xrpl {
/** Transaction flags.
These flags are specified in a transaction's 'Flags' field and modify
the behavior of that transaction.
These flags are specified in a transaction's 'Flags' field and modify the
behavior of that transaction.
There are two types of flags:
(1) Universal flags: these are flags which apply to, and are interpreted the same way by,
all transactions, except, perhaps, to special pseudo-transactions.
(1) Universal flags: these are flags which apply to, and are interpreted
the same way by, all transactions, except, perhaps,
to special pseudo-transactions.
(2) Tx-Specific flags: these are flags which are interpreted according to the type of the
transaction being executed. That is, the same numerical flag value may have different
effects, depending on the transaction being executed.
(2) Tx-Specific flags: these are flags which are interpreted according
to the type of the transaction being executed.
That is, the same numerical flag value may have
different effects, depending on the transaction
being executed.
@note The universal transaction flags occupy the high-order 8 bits.
The tx-specific flags occupy the remaining 24 bits.
@note The universal transaction flags occupy the high-order 8 bits. The
tx-specific flags occupy the remaining 24 bits.
@warning Transaction flags form part of the protocol.
**Changing them should be avoided because without special handling, this will result in
a hard fork.**
@warning Transaction flags form part of the protocol. **Changing them
should be avoided because without special handling, this will
result in a hard fork.**
@ingroup protocol
*/
using FlagValue = std::uint32_t;
// Universal Transaction flags:
inline constexpr FlagValue tfFullyCanonicalSig = 0x80000000;
inline constexpr FlagValue tfInnerBatchTxn = 0x40000000;
inline constexpr FlagValue tfUniversal = tfFullyCanonicalSig | tfInnerBatchTxn;
inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
#pragma push_macro("XMACRO")
#pragma push_macro("TO_VALUE")
#pragma push_macro("VALUE_TO_MAP")
#pragma push_macro("NULL_NAME")
#pragma push_macro("NULL_OUTPUT")
#pragma push_macro("TO_MAP")
#pragma push_macro("TO_MASK")
#pragma push_macro("VALUE_TO_MASK")
#pragma push_macro("ALL_TX_FLAGS")
#pragma push_macro("NULL_MASK_ADJ")
#pragma push_macro("MASK_ADJ_TO_MASK")
#undef XMACRO
#undef TO_VALUE
#undef VALUE_TO_MAP
#undef NULL_NAME
#undef NULL_OUTPUT
#undef TO_MAP
#undef TO_MASK
#undef VALUE_TO_MASK
#undef NULL_MASK_ADJ
#undef MASK_ADJ_TO_MASK
// Formatting equals sign aligned 4 spaces after longest prefix, except for
// wrapped lines
// clang-format off
#undef ALL_TX_FLAGS
// Universal Transaction flags:
constexpr std::uint32_t tfFullyCanonicalSig = 0x80000000;
constexpr std::uint32_t tfInnerBatchTxn = 0x40000000;
constexpr std::uint32_t tfUniversal = tfFullyCanonicalSig | tfInnerBatchTxn;
constexpr std::uint32_t tfUniversalMask = ~tfUniversal;
// XMACRO parameters:
// - TRANSACTION: handles the transaction name, its flags, and mask adjustment
// - TF_FLAG: defines a new flag constant
// - TF_FLAG2: references an existing flag constant (no new definition)
// - MASK_ADJ: specifies flags to add back to the mask (making them invalid for this tx type)
//
// Note: MASK_ADJ is used when a universal flag should be invalid for a specific transaction.
// For example, Batch uses MASK_ADJ(tfInnerBatchTxn) because the outer Batch transaction
// must not have tfInnerBatchTxn set (only inner transactions should have it).
//
// TODO: Consider rewriting this using reflection in C++26 or later. Alternatively this could be a DSL processed by a script at build time.
#define XMACRO(TRANSACTION, TF_FLAG, TF_FLAG2, MASK_ADJ) \
TRANSACTION(AccountSet, \
TF_FLAG(tfRequireDestTag, 0x00010000) \
TF_FLAG(tfOptionalDestTag, 0x00020000) \
TF_FLAG(tfRequireAuth, 0x00040000) \
TF_FLAG(tfOptionalAuth, 0x00080000) \
TF_FLAG(tfDisallowXRP, 0x00100000) \
TF_FLAG(tfAllowXRP, 0x00200000), \
MASK_ADJ(0)) \
\
TRANSACTION(OfferCreate, \
TF_FLAG(tfPassive, 0x00010000) \
TF_FLAG(tfImmediateOrCancel, 0x00020000) \
TF_FLAG(tfFillOrKill, 0x00040000) \
TF_FLAG(tfSell, 0x00080000) \
TF_FLAG(tfHybrid, 0x00100000), \
MASK_ADJ(0)) \
\
TRANSACTION(Payment, \
TF_FLAG(tfNoRippleDirect, 0x00010000) \
TF_FLAG(tfPartialPayment, 0x00020000) \
TF_FLAG(tfLimitQuality, 0x00040000), \
MASK_ADJ(0)) \
\
TRANSACTION(TrustSet, \
TF_FLAG(tfSetfAuth, 0x00010000) \
TF_FLAG(tfSetNoRipple, 0x00020000) \
TF_FLAG(tfClearNoRipple, 0x00040000) \
TF_FLAG(tfSetFreeze, 0x00100000) \
TF_FLAG(tfClearFreeze, 0x00200000) \
TF_FLAG(tfSetDeepFreeze, 0x00400000) \
TF_FLAG(tfClearDeepFreeze, 0x00800000), \
MASK_ADJ(0)) \
\
TRANSACTION(EnableAmendment, \
TF_FLAG(tfGotMajority, 0x00010000) \
TF_FLAG(tfLostMajority, 0x00020000), \
MASK_ADJ(0)) \
\
TRANSACTION(PaymentChannelClaim, \
TF_FLAG(tfRenew, 0x00010000) \
TF_FLAG(tfClose, 0x00020000), \
MASK_ADJ(0)) \
\
TRANSACTION(NFTokenMint, \
TF_FLAG(tfBurnable, 0x00000001) \
TF_FLAG(tfOnlyXRP, 0x00000002) \
/* deprecated TF_FLAG(tfTrustLine, 0x00000004) */ \
TF_FLAG(tfTransferable, 0x00000008) \
TF_FLAG(tfMutable, 0x00000010), \
MASK_ADJ(0)) \
\
TRANSACTION(MPTokenIssuanceCreate, \
/* Note: tf/lsfMPTLocked is intentionally omitted since this transaction is not allowed to modify it. */ \
TF_FLAG(tfMPTCanLock, lsfMPTCanLock) \
TF_FLAG(tfMPTRequireAuth, lsfMPTRequireAuth) \
TF_FLAG(tfMPTCanEscrow, lsfMPTCanEscrow) \
TF_FLAG(tfMPTCanTrade, lsfMPTCanTrade) \
TF_FLAG(tfMPTCanTransfer, lsfMPTCanTransfer) \
TF_FLAG(tfMPTCanClawback, lsfMPTCanClawback), \
MASK_ADJ(0)) \
\
TRANSACTION(MPTokenAuthorize, \
TF_FLAG(tfMPTUnauthorize, 0x00000001), \
MASK_ADJ(0)) \
\
TRANSACTION(MPTokenIssuanceSet, \
TF_FLAG(tfMPTLock, 0x00000001) \
TF_FLAG(tfMPTUnlock, 0x00000002), \
MASK_ADJ(0)) \
\
TRANSACTION(NFTokenCreateOffer, \
TF_FLAG(tfSellNFToken, 0x00000001), \
MASK_ADJ(0)) \
\
TRANSACTION(AMMDeposit, \
TF_FLAG(tfLPToken, 0x00010000) \
TF_FLAG(tfSingleAsset, 0x00080000) \
TF_FLAG(tfTwoAsset, 0x00100000) \
TF_FLAG(tfOneAssetLPToken, 0x00200000) \
TF_FLAG(tfLimitLPToken, 0x00400000) \
TF_FLAG(tfTwoAssetIfEmpty, 0x00800000), \
MASK_ADJ(0)) \
\
TRANSACTION(AMMWithdraw, \
TF_FLAG2(tfLPToken, 0x00010000) \
TF_FLAG(tfWithdrawAll, 0x00020000) \
TF_FLAG(tfOneAssetWithdrawAll, 0x00040000) \
TF_FLAG2(tfSingleAsset, 0x00080000) \
TF_FLAG2(tfTwoAsset, 0x00100000) \
TF_FLAG2(tfOneAssetLPToken, 0x00200000) \
TF_FLAG2(tfLimitLPToken, 0x00400000), \
MASK_ADJ(0)) \
\
TRANSACTION(AMMClawback, \
TF_FLAG(tfClawTwoAssets, 0x00000001), \
MASK_ADJ(0)) \
\
TRANSACTION(XChainModifyBridge, \
TF_FLAG(tfClearAccountCreateAmount, 0x00010000), \
MASK_ADJ(0)) \
\
TRANSACTION(VaultCreate, \
TF_FLAG(tfVaultPrivate, lsfVaultPrivate) \
TF_FLAG(tfVaultShareNonTransferable, 0x00020000), \
MASK_ADJ(0)) \
\
TRANSACTION(Batch, \
TF_FLAG(tfAllOrNothing, 0x00010000) \
TF_FLAG(tfOnlyOne, 0x00020000) \
TF_FLAG(tfUntilFailure, 0x00040000) \
TF_FLAG(tfIndependent, 0x00080000), \
MASK_ADJ(tfInnerBatchTxn)) /* Batch must reject tfInnerBatchTxn - only inner transactions should have this flag */ \
\
TRANSACTION(LoanSet, /* True indicates the loan supports overpayments */ \
TF_FLAG(tfLoanOverpayment, 0x00010000), \
MASK_ADJ(0)) \
\
TRANSACTION(LoanPay, /* True indicates any excess in this payment can be used as an overpayment. */ \
/* False: no overpayments will be taken. */ \
TF_FLAG2(tfLoanOverpayment, 0x00010000) \
TF_FLAG(tfLoanFullPayment, 0x00020000) /* True indicates that the payment is an early full payment. */ \
/* It must pay the entire loan including close interest and fees, or it will fail. */ \
/* False: Not a full payment. */ \
TF_FLAG(tfLoanLatePayment, 0x00040000), /* True indicates that the payment is late, and includes late interest and fees. */ \
/* If the loan is not late, it will fail. */ \
/* False: not a late payment. If the current payment is overdue, the transaction will fail.*/ \
MASK_ADJ(0)) \
\
TRANSACTION(LoanManage, \
TF_FLAG(tfLoanDefault, 0x00010000) \
TF_FLAG(tfLoanImpair, 0x00020000) \
TF_FLAG(tfLoanUnimpair, 0x00040000), \
MASK_ADJ(0))
// AccountSet flags:
constexpr std::uint32_t tfRequireDestTag = 0x00010000;
constexpr std::uint32_t tfOptionalDestTag = 0x00020000;
constexpr std::uint32_t tfRequireAuth = 0x00040000;
constexpr std::uint32_t tfOptionalAuth = 0x00080000;
constexpr std::uint32_t tfDisallowXRP = 0x00100000;
constexpr std::uint32_t tfAllowXRP = 0x00200000;
constexpr std::uint32_t tfAccountSetMask =
~(tfUniversal | tfRequireDestTag | tfOptionalDestTag | tfRequireAuth |
tfOptionalAuth | tfDisallowXRP | tfAllowXRP);
// clang-format on
// AccountSet SetFlag/ClearFlag values
constexpr std::uint32_t asfRequireDest = 1;
constexpr std::uint32_t asfRequireAuth = 2;
constexpr std::uint32_t asfDisallowXRP = 3;
constexpr std::uint32_t asfDisableMaster = 4;
constexpr std::uint32_t asfAccountTxnID = 5;
constexpr std::uint32_t asfNoFreeze = 6;
constexpr std::uint32_t asfGlobalFreeze = 7;
constexpr std::uint32_t asfDefaultRipple = 8;
constexpr std::uint32_t asfDepositAuth = 9;
constexpr std::uint32_t asfAuthorizedNFTokenMinter = 10;
/* // reserved for Hooks amendment
constexpr std::uint32_t asfTshCollect = 11;
*/
constexpr std::uint32_t asfDisallowIncomingNFTokenOffer = 12;
constexpr std::uint32_t asfDisallowIncomingCheck = 13;
constexpr std::uint32_t asfDisallowIncomingPayChan = 14;
constexpr std::uint32_t asfDisallowIncomingTrustline = 15;
constexpr std::uint32_t asfAllowTrustLineClawback = 16;
constexpr std::uint32_t asfAllowTrustLineLocking = 17;
// Create all the flag values.
//
// example:
// inline constexpr FlagValue tfAccountSetRequireDestTag = 0x00010000;
#define TO_VALUE(name, value) inline constexpr FlagValue name = value;
#define NULL_NAME(name, values, maskAdj) values
#define NULL_OUTPUT(name, value)
#define NULL_MASK_ADJ(value)
XMACRO(NULL_NAME, TO_VALUE, NULL_OUTPUT, NULL_MASK_ADJ)
// OfferCreate flags:
constexpr std::uint32_t tfPassive = 0x00010000;
constexpr std::uint32_t tfImmediateOrCancel = 0x00020000;
constexpr std::uint32_t tfFillOrKill = 0x00040000;
constexpr std::uint32_t tfSell = 0x00080000;
constexpr std::uint32_t tfHybrid = 0x00100000;
constexpr std::uint32_t tfOfferCreateMask =
~(tfUniversal | tfPassive | tfImmediateOrCancel | tfFillOrKill | tfSell | tfHybrid);
// Create masks for each transaction type that has flags.
//
// example:
// inline constexpr FlagValue tfAccountSetMask = ~(tfUniversal | tfRequireDestTag |
// tfOptionalDestTag | tfRequireAuth | tfOptionalAuth | tfDisallowXRP | tfAllowXRP);
//
// The mask adjustment (maskAdj) allows adding flags back to the mask, making them invalid.
// For example, Batch uses MASK_ADJ(tfInnerBatchTxn) to reject tfInnerBatchTxn on outer Batch.
#define TO_MASK(name, values, maskAdj) \
inline constexpr FlagValue tf##name##Mask = ~(tfUniversal values) | maskAdj;
#define VALUE_TO_MASK(name, value) | name
#define MASK_ADJ_TO_MASK(value) value
XMACRO(TO_MASK, VALUE_TO_MASK, VALUE_TO_MASK, MASK_ADJ_TO_MASK)
// Payment flags:
constexpr std::uint32_t tfNoRippleDirect = 0x00010000;
constexpr std::uint32_t tfPartialPayment = 0x00020000;
constexpr std::uint32_t tfLimitQuality = 0x00040000;
constexpr std::uint32_t tfPaymentMask =
~(tfUniversal | tfPartialPayment | tfLimitQuality | tfNoRippleDirect);
constexpr std::uint32_t tfMPTPaymentMask = ~(tfUniversal | tfPartialPayment);
// Verify that tfBatchMask correctly rejects tfInnerBatchTxn.
// The outer Batch transaction must NOT have tfInnerBatchTxn set; only inner transactions should
// have it.
static_assert(
(tfBatchMask & tfInnerBatchTxn) == tfInnerBatchTxn,
"tfBatchMask must include tfInnerBatchTxn to reject it on outer Batch");
// TrustSet flags:
constexpr std::uint32_t tfSetfAuth = 0x00010000;
constexpr std::uint32_t tfSetNoRipple = 0x00020000;
constexpr std::uint32_t tfClearNoRipple = 0x00040000;
constexpr std::uint32_t tfSetFreeze = 0x00100000;
constexpr std::uint32_t tfClearFreeze = 0x00200000;
constexpr std::uint32_t tfSetDeepFreeze = 0x00400000;
constexpr std::uint32_t tfClearDeepFreeze = 0x00800000;
constexpr std::uint32_t tfTrustSetMask =
~(tfUniversal | tfSetfAuth | tfSetNoRipple | tfClearNoRipple | tfSetFreeze |
tfClearFreeze | tfSetDeepFreeze | tfClearDeepFreeze);
constexpr std::uint32_t tfTrustSetPermissionMask = ~(tfUniversal | tfSetfAuth | tfSetFreeze | tfClearFreeze);
// Verify that other transaction masks correctly allow tfInnerBatchTxn.
// Inner transactions need tfInnerBatchTxn to be valid, so these masks must not reject it.
static_assert(
(tfPaymentMask & tfInnerBatchTxn) == 0,
"tfPaymentMask must not reject tfInnerBatchTxn");
static_assert(
(tfAccountSetMask & tfInnerBatchTxn) == 0,
"tfAccountSetMask must not reject tfInnerBatchTxn");
// EnableAmendment flags:
constexpr std::uint32_t tfGotMajority = 0x00010000;
constexpr std::uint32_t tfLostMajority = 0x00020000;
constexpr std::uint32_t tfChangeMask =
~( tfUniversal | tfGotMajority | tfLostMajority);
// Create getter functions for each set of flags using Meyer's singleton pattern.
// This avoids static initialization order fiasco while still providing efficient access.
// This is used below in `getAllTxFlags()` to generate the server_definitions RPC
// output.
//
// example:
// inline FlagMap const& getAccountSetFlags() {
// static FlagMap const flags = {
// {"tfRequireDestTag", 0x00010000},
// {"tfOptionalDestTag", 0x00020000},
// ...};
// return flags;
// }
using FlagMap = std::map<std::string, FlagValue>;
#define VALUE_TO_MAP(name, value) {#name, value},
#define TO_MAP(name, values, maskAdj) \
inline FlagMap const& get##name##Flags() \
{ \
static FlagMap const flags = {values}; \
return flags; \
}
XMACRO(TO_MAP, VALUE_TO_MAP, VALUE_TO_MAP, NULL_MASK_ADJ)
// PaymentChannelClaim flags:
constexpr std::uint32_t tfRenew = 0x00010000;
constexpr std::uint32_t tfClose = 0x00020000;
constexpr std::uint32_t tfPayChanClaimMask = ~(tfUniversal | tfRenew | tfClose);
inline FlagMap const&
getUniversalFlags()
{
static FlagMap const flags = {
{"tfFullyCanonicalSig", tfFullyCanonicalSig}, {"tfInnerBatchTxn", tfInnerBatchTxn}};
return flags;
}
// NFTokenMint flags:
constexpr std::uint32_t const tfBurnable = 0x00000001;
constexpr std::uint32_t const tfOnlyXRP = 0x00000002;
constexpr std::uint32_t const tfTrustLine = 0x00000004;
constexpr std::uint32_t const tfTransferable = 0x00000008;
constexpr std::uint32_t const tfMutable = 0x00000010;
// Create a getter function for all transaction flag maps using Meyer's singleton pattern.
// This is used to generate the server_definitions RPC output.
//
// example:
// inline FlagMapPairList const& getAllTxFlags() {
// static FlagMapPairList const flags = {
// {"AccountSet", getAccountSetFlags()},
// ...};
// return flags;
// }
using FlagMapPairList = std::vector<std::pair<std::string, FlagMap>>;
#define ALL_TX_FLAGS(name, values, maskAdj) {#name, get##name##Flags()},
inline FlagMapPairList const&
getAllTxFlags()
{
static FlagMapPairList const flags = {
{"universal", getUniversalFlags()},
XMACRO(ALL_TX_FLAGS, NULL_OUTPUT, NULL_OUTPUT, NULL_MASK_ADJ)};
return flags;
}
#undef XMACRO
#undef TO_VALUE
#undef VALUE_TO_MAP
#undef NULL_NAME
#undef NULL_OUTPUT
#undef TO_MAP
#undef TO_MASK
#undef VALUE_TO_MASK
#undef ALL_TX_FLAGS
#undef NULL_MASK_ADJ
#undef MASK_ADJ_TO_MASK
#pragma pop_macro("XMACRO")
#pragma pop_macro("TO_VALUE")
#pragma pop_macro("VALUE_TO_MAP")
#pragma pop_macro("NULL_NAME")
#pragma pop_macro("NULL_OUTPUT")
#pragma pop_macro("TO_MAP")
#pragma pop_macro("TO_MASK")
#pragma pop_macro("VALUE_TO_MASK")
#pragma pop_macro("ALL_TX_FLAGS")
#pragma pop_macro("NULL_MASK_ADJ")
#pragma pop_macro("MASK_ADJ_TO_MASK")
// Additional transaction masks and combos
inline constexpr FlagValue tfMPTPaymentMask = ~(tfUniversal | tfPartialPayment);
inline constexpr FlagValue tfTrustSetPermissionMask =
~(tfUniversal | tfSetfAuth | tfSetFreeze | tfClearFreeze);
// MPTokenIssuanceCreate flags:
// Note: tf/lsfMPTLocked is intentionally omitted, since this transaction
// is not allowed to modify it.
constexpr std::uint32_t const tfMPTCanLock = lsfMPTCanLock;
constexpr std::uint32_t const tfMPTRequireAuth = lsfMPTRequireAuth;
constexpr std::uint32_t const tfMPTCanEscrow = lsfMPTCanEscrow;
constexpr std::uint32_t const tfMPTCanTrade = lsfMPTCanTrade;
constexpr std::uint32_t const tfMPTCanTransfer = lsfMPTCanTransfer;
constexpr std::uint32_t const tfMPTCanClawback = lsfMPTCanClawback;
constexpr std::uint32_t const tfMPTokenIssuanceCreateMask =
~(tfUniversal | tfMPTCanLock | tfMPTRequireAuth | tfMPTCanEscrow | tfMPTCanTrade | tfMPTCanTransfer | tfMPTCanClawback);
// MPTokenIssuanceCreate MutableFlags:
// Indicating specific fields or flags may be changed after issuance.
inline constexpr FlagValue tmfMPTCanMutateCanLock = lsmfMPTCanMutateCanLock;
inline constexpr FlagValue tmfMPTCanMutateRequireAuth = lsmfMPTCanMutateRequireAuth;
inline constexpr FlagValue tmfMPTCanMutateCanEscrow = lsmfMPTCanMutateCanEscrow;
inline constexpr FlagValue tmfMPTCanMutateCanTrade = lsmfMPTCanMutateCanTrade;
inline constexpr FlagValue tmfMPTCanMutateCanTransfer = lsmfMPTCanMutateCanTransfer;
inline constexpr FlagValue tmfMPTCanMutateCanClawback = lsmfMPTCanMutateCanClawback;
inline constexpr FlagValue tmfMPTCanMutateMetadata = lsmfMPTCanMutateMetadata;
inline constexpr FlagValue tmfMPTCanMutateTransferFee = lsmfMPTCanMutateTransferFee;
inline constexpr FlagValue tmfMPTokenIssuanceCreateMutableMask =
~(tmfMPTCanMutateCanLock | tmfMPTCanMutateRequireAuth | tmfMPTCanMutateCanEscrow |
tmfMPTCanMutateCanTrade | tmfMPTCanMutateCanTransfer | tmfMPTCanMutateCanClawback |
tmfMPTCanMutateMetadata | tmfMPTCanMutateTransferFee);
constexpr std::uint32_t const tmfMPTCanMutateCanLock = lsmfMPTCanMutateCanLock;
constexpr std::uint32_t const tmfMPTCanMutateRequireAuth = lsmfMPTCanMutateRequireAuth;
constexpr std::uint32_t const tmfMPTCanMutateCanEscrow = lsmfMPTCanMutateCanEscrow;
constexpr std::uint32_t const tmfMPTCanMutateCanTrade = lsmfMPTCanMutateCanTrade;
constexpr std::uint32_t const tmfMPTCanMutateCanTransfer = lsmfMPTCanMutateCanTransfer;
constexpr std::uint32_t const tmfMPTCanMutateCanClawback = lsmfMPTCanMutateCanClawback;
constexpr std::uint32_t const tmfMPTCanMutateMetadata = lsmfMPTCanMutateMetadata;
constexpr std::uint32_t const tmfMPTCanMutateTransferFee = lsmfMPTCanMutateTransferFee;
constexpr std::uint32_t const tmfMPTokenIssuanceCreateMutableMask =
~(tmfMPTCanMutateCanLock | tmfMPTCanMutateRequireAuth | tmfMPTCanMutateCanEscrow | tmfMPTCanMutateCanTrade
| tmfMPTCanMutateCanTransfer | tmfMPTCanMutateCanClawback | tmfMPTCanMutateMetadata | tmfMPTCanMutateTransferFee);
// MPTokenAuthorize flags:
constexpr std::uint32_t const tfMPTUnauthorize = 0x00000001;
constexpr std::uint32_t const tfMPTokenAuthorizeMask = ~(tfUniversal | tfMPTUnauthorize);
// MPTokenIssuanceSet flags:
constexpr std::uint32_t const tfMPTLock = 0x00000001;
constexpr std::uint32_t const tfMPTUnlock = 0x00000002;
constexpr std::uint32_t const tfMPTokenIssuanceSetMask = ~(tfUniversal | tfMPTLock | tfMPTUnlock);
constexpr std::uint32_t const tfMPTokenIssuanceSetPermissionMask = ~(tfUniversal | tfMPTLock | tfMPTUnlock);
// MPTokenIssuanceSet MutableFlags:
// Set or Clear flags.
constexpr std::uint32_t const tmfMPTSetCanLock = 0x00000001;
constexpr std::uint32_t const tmfMPTClearCanLock = 0x00000002;
constexpr std::uint32_t const tmfMPTSetRequireAuth = 0x00000004;
constexpr std::uint32_t const tmfMPTClearRequireAuth = 0x00000008;
constexpr std::uint32_t const tmfMPTSetCanEscrow = 0x00000010;
constexpr std::uint32_t const tmfMPTClearCanEscrow = 0x00000020;
constexpr std::uint32_t const tmfMPTSetCanTrade = 0x00000040;
constexpr std::uint32_t const tmfMPTClearCanTrade = 0x00000080;
constexpr std::uint32_t const tmfMPTSetCanTransfer = 0x00000100;
constexpr std::uint32_t const tmfMPTClearCanTransfer = 0x00000200;
constexpr std::uint32_t const tmfMPTSetCanClawback = 0x00000400;
constexpr std::uint32_t const tmfMPTClearCanClawback = 0x00000800;
constexpr std::uint32_t const tmfMPTokenIssuanceSetMutableMask = ~(tmfMPTSetCanLock | tmfMPTClearCanLock |
tmfMPTSetRequireAuth | tmfMPTClearRequireAuth | tmfMPTSetCanEscrow | tmfMPTClearCanEscrow |
tmfMPTSetCanTrade | tmfMPTClearCanTrade | tmfMPTSetCanTransfer | tmfMPTClearCanTransfer |
tmfMPTSetCanClawback | tmfMPTClearCanClawback);
inline constexpr FlagValue tmfMPTSetCanLock = 0x00000001;
inline constexpr FlagValue tmfMPTClearCanLock = 0x00000002;
inline constexpr FlagValue tmfMPTSetRequireAuth = 0x00000004;
inline constexpr FlagValue tmfMPTClearRequireAuth = 0x00000008;
inline constexpr FlagValue tmfMPTSetCanEscrow = 0x00000010;
inline constexpr FlagValue tmfMPTClearCanEscrow = 0x00000020;
inline constexpr FlagValue tmfMPTSetCanTrade = 0x00000040;
inline constexpr FlagValue tmfMPTClearCanTrade = 0x00000080;
inline constexpr FlagValue tmfMPTSetCanTransfer = 0x00000100;
inline constexpr FlagValue tmfMPTClearCanTransfer = 0x00000200;
inline constexpr FlagValue tmfMPTSetCanClawback = 0x00000400;
inline constexpr FlagValue tmfMPTClearCanClawback = 0x00000800;
inline constexpr FlagValue tmfMPTokenIssuanceSetMutableMask = ~(
tmfMPTSetCanLock | tmfMPTClearCanLock | tmfMPTSetRequireAuth | tmfMPTClearRequireAuth |
tmfMPTSetCanEscrow | tmfMPTClearCanEscrow | tmfMPTSetCanTrade | tmfMPTClearCanTrade |
tmfMPTSetCanTransfer | tmfMPTClearCanTransfer | tmfMPTSetCanClawback | tmfMPTClearCanClawback);
// MPTokenIssuanceDestroy flags:
constexpr std::uint32_t const tfMPTokenIssuanceDestroyMask = ~tfUniversal;
// Prior to fixRemoveNFTokenAutoTrustLine, transfer of an NFToken between accounts allowed a
// TrustLine to be added to the issuer of that token without explicit permission from that issuer.
// This was enabled by minting the NFToken with the tfTrustLine flag set.
// Prior to fixRemoveNFTokenAutoTrustLine, transfer of an NFToken between
// accounts allowed a TrustLine to be added to the issuer of that token
// without explicit permission from that issuer. This was enabled by
// minting the NFToken with the tfTrustLine flag set.
//
// That capability could be used to attack the NFToken issuer.
// It would be possible for two accounts to trade the NFToken back and forth building up any number
// of TrustLines on the issuer, increasing the issuer's reserve without bound.
// That capability could be used to attack the NFToken issuer. It
// would be possible for two accounts to trade the NFToken back and forth
// building up any number of TrustLines on the issuer, increasing the
// issuer's reserve without bound.
//
// The fixRemoveNFTokenAutoTrustLine amendment disables minting with the tfTrustLine flag as a way
// to prevent the attack. But until the amendment passes we still need to keep the old behavior
// available.
inline constexpr FlagValue tfTrustLine = 0x00000004; // needed for backwards compatibility
inline constexpr FlagValue tfNFTokenMintMaskWithoutMutable =
// The fixRemoveNFTokenAutoTrustLine amendment disables minting with the
// tfTrustLine flag as a way to prevent the attack. But until the
// amendment passes we still need to keep the old behavior available.
constexpr std::uint32_t const tfNFTokenMintMask =
~(tfUniversal | tfBurnable | tfOnlyXRP | tfTransferable);
inline constexpr FlagValue tfNFTokenMintOldMask = ~(~tfNFTokenMintMaskWithoutMutable | tfTrustLine);
constexpr std::uint32_t const tfNFTokenMintOldMask =
~( ~tfNFTokenMintMask | tfTrustLine);
// if featureDynamicNFT enabled then new flag allowing mutable URI available.
inline constexpr FlagValue tfNFTokenMintOldMaskWithMutable = ~(~tfNFTokenMintOldMask | tfMutable);
constexpr std::uint32_t const tfNFTokenMintOldMaskWithMutable =
~( ~tfNFTokenMintOldMask | tfMutable);
inline constexpr FlagValue tfWithdrawSubTx = tfLPToken | tfSingleAsset | tfTwoAsset |
tfOneAssetLPToken | tfLimitLPToken | tfWithdrawAll | tfOneAssetWithdrawAll;
inline constexpr FlagValue tfDepositSubTx =
tfLPToken | tfSingleAsset | tfTwoAsset | tfOneAssetLPToken | tfLimitLPToken | tfTwoAssetIfEmpty;
constexpr std::uint32_t const tfNFTokenMintMaskWithMutable =
~( ~tfNFTokenMintMask | tfMutable);
#pragma push_macro("ACCOUNTSET_FLAGS")
#pragma push_macro("ACCOUNTSET_FLAG_TO_VALUE")
#pragma push_macro("ACCOUNTSET_FLAG_TO_MAP")
// NFTokenCreateOffer flags:
constexpr std::uint32_t const tfSellNFToken = 0x00000001;
constexpr std::uint32_t const tfNFTokenCreateOfferMask =
~(tfUniversal | tfSellNFToken);
// AccountSet SetFlag/ClearFlag values
#define ACCOUNTSET_FLAGS(ASF_FLAG) \
ASF_FLAG(asfRequireDest, 1) \
ASF_FLAG(asfRequireAuth, 2) \
ASF_FLAG(asfDisallowXRP, 3) \
ASF_FLAG(asfDisableMaster, 4) \
ASF_FLAG(asfAccountTxnID, 5) \
ASF_FLAG(asfNoFreeze, 6) \
ASF_FLAG(asfGlobalFreeze, 7) \
ASF_FLAG(asfDefaultRipple, 8) \
ASF_FLAG(asfDepositAuth, 9) \
ASF_FLAG(asfAuthorizedNFTokenMinter, 10) \
/* 11 is reserved for Hooks amendment */ \
/* ASF_FLAG(asfTshCollect, 11) */ \
ASF_FLAG(asfDisallowIncomingNFTokenOffer, 12) \
ASF_FLAG(asfDisallowIncomingCheck, 13) \
ASF_FLAG(asfDisallowIncomingPayChan, 14) \
ASF_FLAG(asfDisallowIncomingTrustline, 15) \
ASF_FLAG(asfAllowTrustLineClawback, 16) \
ASF_FLAG(asfAllowTrustLineLocking, 17)
// NFTokenCancelOffer flags:
constexpr std::uint32_t const tfNFTokenCancelOfferMask = ~tfUniversal;
#define ACCOUNTSET_FLAG_TO_VALUE(name, value) inline constexpr FlagValue name = value;
#define ACCOUNTSET_FLAG_TO_MAP(name, value) {#name, value},
// NFTokenAcceptOffer flags:
constexpr std::uint32_t const tfNFTokenAcceptOfferMask = ~tfUniversal;
ACCOUNTSET_FLAGS(ACCOUNTSET_FLAG_TO_VALUE)
// Clawback flags:
constexpr std::uint32_t const tfClawbackMask = ~tfUniversal;
inline std::map<std::string, FlagValue> const&
getAsfFlagMap()
{
static std::map<std::string, FlagValue> const flags = {
ACCOUNTSET_FLAGS(ACCOUNTSET_FLAG_TO_MAP)};
return flags;
}
// AMM Flags:
constexpr std::uint32_t tfLPToken = 0x00010000;
constexpr std::uint32_t tfWithdrawAll = 0x00020000;
constexpr std::uint32_t tfOneAssetWithdrawAll = 0x00040000;
constexpr std::uint32_t tfSingleAsset = 0x00080000;
constexpr std::uint32_t tfTwoAsset = 0x00100000;
constexpr std::uint32_t tfOneAssetLPToken = 0x00200000;
constexpr std::uint32_t tfLimitLPToken = 0x00400000;
constexpr std::uint32_t tfTwoAssetIfEmpty = 0x00800000;
constexpr std::uint32_t tfWithdrawSubTx =
tfLPToken | tfSingleAsset | tfTwoAsset | tfOneAssetLPToken |
tfLimitLPToken | tfWithdrawAll | tfOneAssetWithdrawAll;
constexpr std::uint32_t tfDepositSubTx =
tfLPToken | tfSingleAsset | tfTwoAsset | tfOneAssetLPToken |
tfLimitLPToken | tfTwoAssetIfEmpty;
constexpr std::uint32_t tfWithdrawMask = ~(tfUniversal | tfWithdrawSubTx);
constexpr std::uint32_t tfDepositMask = ~(tfUniversal | tfDepositSubTx);
#undef ACCOUNTSET_FLAG_TO_VALUE
#undef ACCOUNTSET_FLAG_TO_MAP
#undef ACCOUNTSET_FLAGS
// AMMClawback flags:
constexpr std::uint32_t tfClawTwoAssets = 0x00000001;
constexpr std::uint32_t tfAMMClawbackMask = ~(tfUniversal | tfClawTwoAssets);
#pragma pop_macro("ACCOUNTSET_FLAG_TO_VALUE")
#pragma pop_macro("ACCOUNTSET_FLAG_TO_MAP")
#pragma pop_macro("ACCOUNTSET_FLAGS")
// BridgeModify flags:
constexpr std::uint32_t tfClearAccountCreateAmount = 0x00010000;
constexpr std::uint32_t tfBridgeModifyMask = ~(tfUniversal | tfClearAccountCreateAmount);
// VaultCreate flags:
constexpr std::uint32_t const tfVaultPrivate = 0x00010000;
static_assert(tfVaultPrivate == lsfVaultPrivate);
constexpr std::uint32_t const tfVaultShareNonTransferable = 0x00020000;
constexpr std::uint32_t const tfVaultCreateMask = ~(tfUniversal | tfVaultPrivate | tfVaultShareNonTransferable);
// Batch Flags:
constexpr std::uint32_t tfAllOrNothing = 0x00010000;
constexpr std::uint32_t tfOnlyOne = 0x00020000;
constexpr std::uint32_t tfUntilFailure = 0x00040000;
constexpr std::uint32_t tfIndependent = 0x00080000;
/**
* @note If nested Batch transactions are supported in the future, the tfInnerBatchTxn flag
* will need to be removed from this mask to allow Batch transaction to be inside
* the sfRawTransactions array.
*/
constexpr std::uint32_t const tfBatchMask =
~(tfUniversal | tfAllOrNothing | tfOnlyOne | tfUntilFailure | tfIndependent) | tfInnerBatchTxn;
// LoanSet and LoanPay flags:
// LoanSet: True, indicates the loan supports overpayments
// LoanPay: True, indicates any excess in this payment can be used
// as an overpayment. False, no overpayments will be taken.
constexpr std::uint32_t const tfLoanOverpayment = 0x00010000;
// LoanPay exclusive flags:
// tfLoanFullPayment: True, indicates that the payment is an early
// full payment. It must pay the entire loan including close
// interest and fees, or it will fail. False: Not a full payment.
constexpr std::uint32_t const tfLoanFullPayment = 0x00020000;
// tfLoanLatePayment: True, indicates that the payment is late,
// and includes late interest and fees. If the loan is not late,
// it will fail. False: not a late payment. If the current payment
// is overdue, the transaction will fail.
constexpr std::uint32_t const tfLoanLatePayment = 0x00040000;
constexpr std::uint32_t const tfLoanSetMask = ~(tfUniversal |
tfLoanOverpayment);
constexpr std::uint32_t const tfLoanPayMask = ~(tfUniversal |
tfLoanOverpayment | tfLoanFullPayment | tfLoanLatePayment);
// LoanManage flags:
constexpr std::uint32_t const tfLoanDefault = 0x00010000;
constexpr std::uint32_t const tfLoanImpair = 0x00020000;
constexpr std::uint32_t const tfLoanUnimpair = 0x00040000;
constexpr std::uint32_t const tfLoanManageMask = ~(tfUniversal | tfLoanDefault | tfLoanImpair | tfLoanUnimpair);
// clang-format on
} // namespace xrpl

View File

@@ -2,8 +2,6 @@
#include <xrpl/protocol/KnownFormats.h>
#include <vector>
namespace xrpl {
/** Transaction type identifiers.
@@ -75,9 +73,6 @@ private:
public:
static TxFormats const&
getInstance();
static std::vector<SOElement> const&
getCommonFields();
};
} // namespace xrpl

View File

@@ -25,7 +25,6 @@ namespace jss {
JSS(AL_size); // out: GetCounts
JSS(AL_hit_rate); // out: GetCounts
JSS(AcceptedCredentials); // out: AccountObjects
JSS(ACCOUNT_SET_FLAGS); // out: RPC server_definitions
JSS(Account); // in: TransactionSign; field.
JSS(AMMID); // field
JSS(Amount); // in: TransactionSign; field.
@@ -188,7 +187,6 @@ JSS(closed_ledger); // out: NetworkOPs
JSS(cluster); // out: PeerImp
JSS(code); // out: errors
JSS(command); // in: RPCHandler
JSS(common); // out: RPC server_definitions
JSS(complete); // out: NetworkOPs, InboundLedger
JSS(complete_ledgers); // out: NetworkOPs, PeerImp
JSS(consensus); // out: NetworkOPs, LedgerConsensus
@@ -358,8 +356,6 @@ JSS(ledger_min); // in, out: AccountTx*
JSS(ledger_time); // out: NetworkOPs
JSS(LEDGER_ENTRY_TYPES); // out: RPC server_definitions
// matches definitions.json format
JSS(LEDGER_ENTRY_FLAGS); // out: RPC server_definitions
JSS(LEDGER_ENTRY_FORMATS); // out: RPC server_definitions
JSS(levels); // LogLevels
JSS(limit); // in/out: AccountTx*, AccountOffers,
// AccountLines, AccountObjects
@@ -461,7 +457,6 @@ JSS(open); // out: handlers/Ledger
JSS(open_ledger_cost); // out: SubmitTransaction
JSS(open_ledger_fee); // out: TxQ
JSS(open_ledger_level); // out: TxQ
JSS(optionality); // out: server_definitions
JSS(oracles); // in: get_aggregate_price
JSS(oracle_document_id); // in: get_aggregate_price
JSS(owner); // in: LedgerEntry, out: NetworkOPs
@@ -621,8 +616,6 @@ JSS(TRANSACTION_RESULTS); // out: RPC server_definitions
// matches definitions.json format
JSS(TRANSACTION_TYPES); // out: RPC server_definitions
// matches definitions.json format
JSS(TRANSACTION_FLAGS); // out: RPC server_definitions
JSS(TRANSACTION_FORMATS); // out: RPC server_definitions
JSS(TYPES); // out: RPC server_definitions
// matches definitions.json format
JSS(transfer_rate); // out: nft_info (clio)

View File

@@ -13,6 +13,9 @@ public:
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);

View File

@@ -13,6 +13,9 @@ public:
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);

View File

@@ -26,6 +26,9 @@ public:
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);

View File

@@ -13,6 +13,9 @@ public:
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);

View File

@@ -9,20 +9,17 @@
#include <iterator>
#include <limits>
#include <numeric>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#ifdef _MSC_VER
#pragma message("Using boost::multiprecision::uint128_t and int128_t")
#include <boost/multiprecision/cpp_int.hpp>
using uint128_t = boost::multiprecision::uint128_t;
using int128_t = boost::multiprecision::int128_t;
#else // !defined(_MSC_VER)
using uint128_t = __uint128_t;
using int128_t = __int128_t;
#endif // !defined(_MSC_VER)
#endif
using uint128_t = xrpl::detail::uint128_t;
using int128_t = xrpl::detail::int128_t;
namespace xrpl {
@@ -61,9 +58,6 @@ Number::setMantissaScale(MantissaRange::mantissa_scale scale)
// 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>;
class Number::Guard
{
std::uint64_t digits_; // 16 decimal guard digits
@@ -99,7 +93,7 @@ public:
round() noexcept;
// Modify the result to the correctly rounded value
template <UnsignedMantissa T>
template <detail::UnsignedMantissa T>
void
doRoundUp(
bool& negative,
@@ -107,22 +101,22 @@ public:
int& exponent,
internalrep const& minMantissa,
internalrep const& maxMantissa,
std::string location);
std::string_view location);
// Modify the result to the correctly rounded value
template <UnsignedMantissa T>
template <detail::UnsignedMantissa T>
void
doRoundDown(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa);
// Modify the result to the correctly rounded value
void
doRound(rep& drops, std::string location);
doRound(internalrep& drops, std::string_view location);
private:
void
doPush(unsigned d) noexcept;
template <UnsignedMantissa T>
template <detail::UnsignedMantissa T>
void
bringIntoRange(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa);
};
@@ -209,7 +203,7 @@ Number::Guard::round() noexcept
return 0;
}
template <UnsignedMantissa T>
template <detail::UnsignedMantissa T>
void
Number::Guard::bringIntoRange(
bool& negative,
@@ -228,13 +222,11 @@ Number::Guard::bringIntoRange(
{
constexpr Number zero = Number{};
negative = zero.negative_;
mantissa = zero.mantissa_;
exponent = zero.exponent_;
std::tie(negative, mantissa, exponent) = zero.toInternal();
}
}
template <UnsignedMantissa T>
template <detail::UnsignedMantissa T>
void
Number::Guard::doRoundUp(
bool& negative,
@@ -242,7 +234,7 @@ Number::Guard::doRoundUp(
int& exponent,
internalrep const& minMantissa,
internalrep const& maxMantissa,
std::string location)
std::string_view location)
{
auto r = round();
if (r == 1 || (r == 0 && (mantissa & 1) == 1))
@@ -250,7 +242,7 @@ Number::Guard::doRoundUp(
++mantissa;
// Ensure mantissa after incrementing fits within both the
// min/maxMantissa range and is a valid "rep".
if (mantissa > maxMantissa || mantissa > maxRep)
if (mantissa > maxMantissa)
{
mantissa /= 10;
++exponent;
@@ -258,10 +250,10 @@ Number::Guard::doRoundUp(
}
bringIntoRange(negative, mantissa, exponent, minMantissa);
if (exponent > maxExponent)
throw std::overflow_error(location);
throw std::overflow_error(std::string{location});
}
template <UnsignedMantissa T>
template <detail::UnsignedMantissa T>
void
Number::Guard::doRoundDown(
bool& negative,
@@ -284,26 +276,25 @@ Number::Guard::doRoundDown(
// Modify the result to the correctly rounded value
void
Number::Guard::doRound(rep& drops, std::string location)
Number::Guard::doRound(internalrep& drops, std::string_view location)
{
auto r = round();
if (r == 1 || (r == 0 && (drops & 1) == 1))
{
if (drops >= maxRep)
auto const& range = range_.get();
if (drops >= range.max)
{
static_assert(sizeof(internalrep) == sizeof(rep));
// This should be impossible, because it's impossible to represent
// "maxRep + 0.6" in Number, regardless of the scale. There aren't
// enough digits available. You'd either get a mantissa of "maxRep"
// or "(maxRep + 1) / 10", neither of which will round up when
// "largestMantissa + 0.6" in Number, regardless of the scale. There aren't
// enough digits available. You'd either get a mantissa of "largestMantissa"
// or "largestMantissa / 10 + 1", neither of which will round up when
// converting to rep, though the latter might overflow _before_
// rounding.
throw std::overflow_error(location); // LCOV_EXCL_LINE
throw std::overflow_error(std::string{location}); // LCOV_EXCL_LINE
}
++drops;
}
if (is_negative())
drops = -drops;
}
// Number
@@ -318,23 +309,135 @@ Number::externalToInternal(rep mantissa)
// If the mantissa is already positive, just return it
if (mantissa >= 0)
return mantissa;
// If the mantissa is negative, but fits within the positive range of rep,
// return it negated
if (mantissa >= -std::numeric_limits<rep>::max())
return -mantissa;
// If the mantissa doesn't fit within the positive range, convert to
// int128_t, negate that, and cast it back down to the internalrep
// In practice, this is only going to cover the case of
// std::numeric_limits<rep>::min().
int128_t temp = mantissa;
return static_cast<internalrep>(-temp);
// Cast to unsigned before negating to avoid undefined behavior when
// mantissa == std::numeric_limits<rep>::min() (INT64_MIN). Negating
// INT64_MIN in signed arithmetic is UB, but casting to the unsigned
// internalrep first makes the operation well-defined.
return -static_cast<internalrep>(mantissa);
}
/** Breaks down the number into components, potentially de-normalizing it.
*
* Ensures that the mantissa always has range_.log + 1 digits.
*
*/
template <detail::UnsignedMantissa Rep>
std::tuple<bool, Rep, int>
Number::toInternal(MantissaRange const& range) const
{
auto exponent = exponent_;
bool const negative = mantissa_ < 0;
// It should be impossible for mantissa_ to be INT64_MIN, but use externalToInternal just in
// case.
Rep mantissa = static_cast<Rep>(externalToInternal(mantissa_));
auto const internalMin = range.internalMin;
auto const minMantissa = range.min;
if (mantissa != 0 && mantissa >= minMantissa && mantissa < internalMin)
{
// Ensure the mantissa has the correct number of digits
mantissa *= 10;
--exponent;
XRPL_ASSERT_PARTS(
mantissa >= internalMin && mantissa < internalMin * 10,
"xrpl::Number::toInternal()",
"Number is within reference range and has 'log' digits");
}
return {negative, mantissa, exponent};
}
/** Breaks down the number into components, potentially de-normalizing it.
*
* Ensures that the mantissa always has exactly range_.log + 1 digits.
*
*/
template <detail::UnsignedMantissa Rep>
std::tuple<bool, Rep, int>
Number::toInternal() const
{
return toInternal(range_);
}
/** Rebuilds the number from components.
*
* If "expectNormal" is true, the values are expected to be normalized - all
* in their valid ranges.
*
* If "expectNormal" is false, the values are expected to be "near
* normalized", meaning that the mantissa has to be modified at most once to
* bring it back into range.
*
*/
template <bool expectNormal, detail::UnsignedMantissa Rep>
void
Number::fromInternal(bool negative, Rep mantissa, int exponent, MantissaRange const* pRange)
{
if constexpr (std::is_same_v<std::bool_constant<expectNormal>, std::false_type>)
{
if (!pRange)
throw std::runtime_error("Missing range to Number::fromInternal!");
auto const& range = *pRange;
auto const maxMantissa = range.max;
auto const minMantissa = range.min;
XRPL_ASSERT_PARTS(
mantissa >= minMantissa, "xrpl::Number::fromInternal", "mantissa large enough");
if (mantissa > maxMantissa || mantissa < minMantissa)
{
normalize(negative, mantissa, exponent, range.min, maxMantissa);
}
XRPL_ASSERT_PARTS(
mantissa >= minMantissa && mantissa <= maxMantissa,
"xrpl::Number::fromInternal",
"mantissa in range");
}
// mantissa is unsigned, but it might not be uint64
mantissa_ = static_cast<rep>(static_cast<internalrep>(mantissa));
if (negative)
mantissa_ = -mantissa_;
exponent_ = exponent;
XRPL_ASSERT_PARTS(
(pRange && isnormal(*pRange)) || isnormal(),
"xrpl::Number::fromInternal",
"Number is normalized");
}
/** Rebuilds the number from components.
*
* If "expectNormal" is true, the values are expected to be normalized - all in
* their valid ranges.
*
* If "expectNormal" is false, the values are expected to be "near normalized",
* meaning that the mantissa has to be modified at most once to bring it back
* into range.
*
*/
template <bool expectNormal, detail::UnsignedMantissa Rep>
void
Number::fromInternal(bool negative, Rep mantissa, int exponent)
{
MantissaRange const* pRange = nullptr;
if constexpr (std::is_same_v<std::bool_constant<expectNormal>, std::false_type>)
{
pRange = &Number::range_.get();
}
fromInternal(negative, mantissa, exponent, pRange);
}
constexpr Number
Number::oneSmall()
{
return Number{false, Number::smallRange.min, -Number::smallRange.log, Number::unchecked{}};
return Number{
false, Number::smallRange.internalMin, -Number::smallRange.log, Number::unchecked{}};
};
constexpr Number oneSml = Number::oneSmall();
@@ -342,103 +445,86 @@ constexpr Number oneSml = Number::oneSmall();
constexpr Number
Number::oneLarge()
{
return Number{false, Number::largeRange.min, -Number::largeRange.log, Number::unchecked{}};
return Number{
false, Number::largeRange.internalMin, -Number::largeRange.log, Number::unchecked{}};
};
constexpr Number oneLrg = Number::oneLarge();
Number
Number::one()
Number::one(MantissaRange const& range)
{
if (&range_.get() == &smallRange)
if (&range == &smallRange)
return oneSml;
XRPL_ASSERT(&range_.get() == &largeRange, "Number::one() : valid range_");
XRPL_ASSERT(&range == &largeRange, "Number::one() : valid range");
return oneLrg;
}
Number
Number::one()
{
return one(range_);
}
// Use the member names in this static function for now so the diff is cleaner
// TODO: Rename the function parameters to get rid of the "_" suffix
template <class T>
void
doNormalize(
bool& negative,
T& mantissa_,
int& exponent_,
T& mantissa,
int& exponent,
MantissaRange::rep const& minMantissa,
MantissaRange::rep const& maxMantissa)
{
auto constexpr minExponent = Number::minExponent;
auto constexpr maxExponent = Number::maxExponent;
auto constexpr maxRep = Number::maxRep;
using Guard = Number::Guard;
constexpr Number zero = Number{};
if (mantissa_ == 0)
auto const& range = Number::range_.get();
if (mantissa == 0 || (mantissa < minMantissa && exponent <= minExponent))
{
mantissa_ = zero.mantissa_;
exponent_ = zero.exponent_;
negative = zero.negative_;
std::tie(negative, mantissa, exponent) = zero.toInternal(range);
return;
}
auto m = mantissa_;
while ((m < minMantissa) && (exponent_ > minExponent))
auto m = mantissa;
while ((m < minMantissa) && (exponent > minExponent))
{
m *= 10;
--exponent_;
--exponent;
}
Guard g;
if (negative)
g.set_negative();
while (m > maxMantissa)
{
if (exponent_ >= maxExponent)
if (exponent >= maxExponent)
throw std::overflow_error("Number::normalize 1");
g.push(m % 10);
m /= 10;
++exponent_;
++exponent;
}
if ((exponent_ < minExponent) || (m < minMantissa))
if ((exponent < minExponent) || (m == 0))
{
mantissa_ = zero.mantissa_;
exponent_ = zero.exponent_;
negative = zero.negative_;
std::tie(negative, mantissa, exponent) = zero.toInternal(range);
return;
}
// When using the largeRange, "m" needs fit within an int64, even if
// the final mantissa_ is going to end up larger to fit within the
// MantissaRange. Cut it down here so that the rounding will be done while
// it's smaller.
//
// Example: 9,900,000,000,000,123,456 > 9,223,372,036,854,775,807,
// so "m" will be modified to 990,000,000,000,012,345. Then that value
// will be rounded to 990,000,000,000,012,345 or
// 990,000,000,000,012,346, depending on the rounding mode. Finally,
// mantissa_ will be "m*10" so it fits within the range, and end up as
// 9,900,000,000,000,123,450 or 9,900,000,000,000,123,460.
// mantissa() will return mantissa_ / 10, and exponent() will return
// exponent_ + 1.
if (m > maxRep)
{
if (exponent_ >= maxExponent)
throw std::overflow_error("Number::normalize 1.5");
g.push(m % 10);
m /= 10;
++exponent_;
}
// Before modification, m should be within the min/max range. After
// modification, it must be less than maxRep. In other words, the original
// value should have been no more than maxRep * 10.
// (maxRep * 10 > maxMantissa)
XRPL_ASSERT_PARTS(m <= maxRep, "xrpl::doNormalize", "intermediate mantissa fits in int64");
mantissa_ = m;
XRPL_ASSERT_PARTS(m <= maxMantissa, "xrpl::doNormalize", "intermediate mantissa fits in int64");
mantissa = m;
g.doRoundUp(negative, mantissa, exponent, minMantissa, maxMantissa, "Number::normalize 2");
g.doRoundUp(negative, mantissa_, exponent_, minMantissa, maxMantissa, "Number::normalize 2");
XRPL_ASSERT_PARTS(
mantissa_ >= minMantissa && mantissa_ <= maxMantissa,
mantissa >= minMantissa && mantissa <= maxMantissa,
"xrpl::doNormalize",
"final mantissa fits in range");
XRPL_ASSERT_PARTS(
exponent >= minExponent && exponent <= maxExponent,
"xrpl::doNormalize",
"final exponent fits in range");
}
template <>
@@ -477,11 +563,20 @@ Number::normalize<unsigned long>(
doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa);
}
void
Number::normalize(MantissaRange const& range)
{
auto [negative, mantissa, exponent] = toInternal(range);
normalize(negative, mantissa, exponent, range.min, range.max);
fromInternal(negative, mantissa, exponent, &range);
}
void
Number::normalize()
{
auto const& range = range_.get();
normalize(negative_, mantissa_, exponent_, range.min, range.max);
normalize(range_);
}
// Copy the number, but set a new exponent. Because the mantissa doesn't change,
@@ -491,21 +586,33 @@ Number
Number::shiftExponent(int exponentDelta) const
{
XRPL_ASSERT_PARTS(isnormal(), "xrpl::Number::shiftExponent", "normalized");
auto const newExponent = exponent_ + exponentDelta;
if (newExponent >= maxExponent)
Number result = *this;
result.exponent_ += exponentDelta;
if (result.exponent_ >= maxExponent)
throw std::overflow_error("Number::shiftExponent");
if (newExponent < minExponent)
if (result.exponent_ < minExponent)
{
return Number{};
}
Number const result{negative_, mantissa_, newExponent, unchecked{}};
XRPL_ASSERT_PARTS(result.isnormal(), "xrpl::Number::shiftExponent", "result is normalized");
return result;
}
Number::Number(bool negative, internalrep mantissa, int exponent, normalized)
{
auto const& range = range_.get();
normalize(negative, mantissa, exponent, range.min, range.max);
fromInternal(negative, mantissa, exponent, &range);
}
Number&
Number::operator+=(Number const& y)
{
auto const& range = range_.get();
constexpr Number zero = Number{};
if (y == zero)
return *this;
@@ -520,7 +627,8 @@ Number::operator+=(Number const& y)
return *this;
}
XRPL_ASSERT(isnormal() && y.isnormal(), "xrpl::Number::operator+=(Number) : is normal");
XRPL_ASSERT(
isnormal(range) && y.isnormal(range), "xrpl::Number::operator+=(Number) : is normal");
// *n = negative
// *s = sign
// *m = mantissa
@@ -528,13 +636,10 @@ Number::operator+=(Number const& y)
// Need to use uint128_t, because large mantissas can overflow when added
// together.
bool xn = negative_;
uint128_t xm = mantissa_;
auto xe = exponent_;
auto [xn, xm, xe] = toInternal<uint128_t>(range);
auto [yn, ym, ye] = y.toInternal<uint128_t>(range);
bool yn = y.negative_;
uint128_t ym = y.mantissa_;
auto ye = y.exponent_;
Guard g;
if (xe < ye)
{
@@ -559,14 +664,13 @@ Number::operator+=(Number const& y)
} while (xe > ye);
}
auto const& range = range_.get();
auto const& minMantissa = range.min;
auto const& maxMantissa = range.max;
if (xn == yn)
{
xm += ym;
if (xm > maxMantissa || xm > maxRep)
if (xm > maxMantissa)
{
g.push(xm % 10);
xm /= 10;
@@ -586,7 +690,7 @@ Number::operator+=(Number const& y)
xe = ye;
xn = yn;
}
while (xm < minMantissa && xm * 10 <= maxRep)
while (xm < minMantissa)
{
xm *= 10;
xm -= g.pop();
@@ -595,10 +699,8 @@ Number::operator+=(Number const& y)
g.doRoundDown(xn, xm, xe, minMantissa);
}
negative_ = xn;
mantissa_ = static_cast<internalrep>(xm);
exponent_ = xe;
normalize();
normalize(xn, xm, xe, minMantissa, maxMantissa);
fromInternal(xn, xm, xe, &range);
return *this;
}
@@ -633,6 +735,8 @@ divu10(uint128_t& u)
Number&
Number::operator*=(Number const& y)
{
auto const& range = range_.get();
constexpr Number zero = Number{};
if (*this == zero)
return *this;
@@ -646,15 +750,11 @@ Number::operator*=(Number const& y)
// *m = mantissa
// *e = exponent
bool xn = negative_;
auto [xn, xm, xe] = toInternal(range);
int xs = xn ? -1 : 1;
internalrep xm = mantissa_;
auto xe = exponent_;
bool yn = y.negative_;
auto [yn, ym, ye] = y.toInternal(range);
int ys = yn ? -1 : 1;
internalrep ym = y.mantissa_;
auto ye = y.exponent_;
auto zm = uint128_t(xm) * uint128_t(ym);
auto ze = xe + ye;
@@ -664,11 +764,10 @@ Number::operator*=(Number const& y)
if (zn)
g.set_negative();
auto const& range = range_.get();
auto const& minMantissa = range.min;
auto const& maxMantissa = range.max;
while (zm > maxMantissa || zm > maxRep)
while (zm > maxMantissa)
{
// The following is optimization for:
// g.push(static_cast<unsigned>(zm % 10));
@@ -685,17 +784,17 @@ Number::operator*=(Number const& y)
minMantissa,
maxMantissa,
"Number::multiplication overflow : exponent is " + std::to_string(xe));
negative_ = zn;
mantissa_ = xm;
exponent_ = xe;
normalize();
normalize(zn, xm, xe, minMantissa, maxMantissa);
fromInternal(zn, xm, xe, &range);
return *this;
}
Number&
Number::operator/=(Number const& y)
{
auto const& range = range_.get();
constexpr Number zero = Number{};
if (y == zero)
throw std::overflow_error("Number: divide by 0");
@@ -708,17 +807,12 @@ Number::operator/=(Number const& y)
// *m = mantissa
// *e = exponent
bool np = negative_;
auto [np, nm, ne] = toInternal(range);
int ns = (np ? -1 : 1);
auto nm = mantissa_;
auto ne = exponent_;
bool dp = y.negative_;
auto [dp, dm, de] = y.toInternal(range);
int ds = (dp ? -1 : 1);
auto dm = y.mantissa_;
auto de = y.exponent_;
auto const& range = range_.get();
auto const& minMantissa = range.min;
auto const& maxMantissa = range.max;
@@ -730,7 +824,7 @@ Number::operator/=(Number const& y)
// f can be up to 10^(38-19) = 10^19 safely
static_assert(smallRange.log == 15);
static_assert(largeRange.log == 18);
bool small = Number::getMantissaScale() == MantissaRange::small;
bool small = range.scale == MantissaRange::small;
uint128_t const f = small ? 100'000'000'000'000'000 : 10'000'000'000'000'000'000ULL;
XRPL_ASSERT_PARTS(f >= minMantissa * 10, "Number::operator/=", "factor expected size");
@@ -780,10 +874,8 @@ Number::operator/=(Number const& y)
}
}
normalize(zn, zm, ze, minMantissa, maxMantissa);
negative_ = zn;
mantissa_ = static_cast<internalrep>(zm);
exponent_ = ze;
XRPL_ASSERT_PARTS(isnormal(), "xrpl::Number::operator/=", "result is normalized");
fromInternal(zn, zm, ze, &range);
XRPL_ASSERT_PARTS(isnormal(range), "xrpl::Number::operator/=", "result is normalized");
return *this;
}
@@ -791,30 +883,36 @@ Number::operator/=(Number const& y)
Number::
operator rep() const
{
rep drops = mantissa();
auto const m = mantissa();
internalrep drops = externalToInternal(m);
if (drops == 0)
return drops;
int offset = exponent();
Guard g;
if (drops != 0)
if (m < 0)
{
if (negative_)
{
g.set_negative();
drops = -drops;
}
for (; offset < 0; ++offset)
{
g.push(drops % 10);
drops /= 10;
}
for (; offset > 0; --offset)
{
if (drops > maxRep / 10)
throw std::overflow_error("Number::operator rep() overflow");
drops *= 10;
}
g.doRound(drops, "Number::operator rep() rounding overflow");
g.set_negative();
}
return drops;
for (; offset < 0; ++offset)
{
g.push(drops % 10);
drops /= 10;
}
for (; offset > 0; --offset)
{
if (drops >= largeRange.min)
throw std::overflow_error("Number::operator rep() overflow");
drops *= 10;
}
g.doRound(drops, "Number::operator rep() rounding overflow");
if (g.is_negative())
return -drops;
else
return drops;
}
Number
@@ -838,19 +936,22 @@ Number::truncate() const noexcept
std::string
to_string(Number const& amount)
{
auto const& range = Number::range_.get();
// keep full internal accuracy, but make more human friendly if possible
constexpr Number zero = Number{};
if (amount == zero)
return "0";
auto exponent = amount.exponent_;
auto mantissa = amount.mantissa_;
bool const negative = amount.negative_;
// The mantissa must have a set number of decimal places for this to work
auto [negative, mantissa, exponent] = amount.toInternal(range);
// Use scientific notation for exponents that are too small or too large
auto const rangeLog = Number::mantissaLog();
if (((exponent != 0) && ((exponent < -(rangeLog + 10)) || (exponent > -(rangeLog - 10)))))
auto const rangeLog = range.log;
if (((exponent != 0 && amount.exponent() != 0) &&
((exponent < -(rangeLog + 10)) || (exponent > -(rangeLog - 10)))))
{
// Remove trailing zeroes from the mantissa.
while (mantissa != 0 && mantissa % 10 == 0 && exponent < Number::maxExponent)
{
mantissa /= 10;
@@ -858,8 +959,11 @@ to_string(Number const& amount)
}
std::string ret = negative ? "-" : "";
ret.append(std::to_string(mantissa));
ret.append(1, 'e');
ret.append(std::to_string(exponent));
if (exponent != 0)
{
ret.append(1, 'e');
ret.append(std::to_string(exponent));
}
return ret;
}
@@ -943,20 +1047,11 @@ power(Number const& f, unsigned n)
return r;
}
// Returns f^(1/d)
// Uses NewtonRaphson iterations until the result stops changing
// to find the non-negative root of the polynomial g(x) = x^d - f
// This function, and power(Number f, unsigned n, unsigned d)
// treat corner cases such as 0 roots as advised by Annex F of
// the C standard, which itself is consistent with the IEEE
// floating point standards.
Number
root(Number f, unsigned d)
Number::root(MantissaRange const& range, Number f, unsigned d)
{
constexpr Number zero = Number{};
auto const one = Number::one();
auto const one = Number::one(range);
if (f == one || d == 1)
return f;
@@ -973,21 +1068,28 @@ root(Number f, unsigned d)
if (f == zero)
return f;
// Scale f into the range (0, 1) such that f's exponent is a multiple of d
auto e = f.exponent_ + Number::mantissaLog() + 1;
auto const di = static_cast<int>(d);
auto ex = [e = e, di = di]() // Euclidean remainder of e/d
{
int k = (e >= 0 ? e : e - (di - 1)) / di;
int k2 = e - k * di;
if (k2 == 0)
return 0;
return di - k2;
}();
e += ex;
f = f.shiftExponent(-e); // f /= 10^e;
auto const [e, di] = [&]() {
auto const exponent = std::get<2>(f.toInternal(range));
XRPL_ASSERT_PARTS(f.isnormal(), "xrpl::root(Number, unsigned)", "f is normalized");
// Scale f into the range (0, 1) such that the scale change (e) is a
// multiple of the root (d)
auto e = exponent + range.log + 1;
auto const di = static_cast<int>(d);
auto ex = [e = e, di = di]() // Euclidean remainder of e/d
{
int k = (e >= 0 ? e : e - (di - 1)) / di;
int k2 = e - k * di;
if (k2 == 0)
return 0;
return di - k2;
}();
e += ex;
f = f.shiftExponent(-e); // f /= 10^e;
return std::make_tuple(e, di);
}();
XRPL_ASSERT_PARTS(e % di == 0, "xrpl::root(Number, unsigned)", "e is divisible by d");
XRPL_ASSERT_PARTS(f.isnormal(range), "xrpl::root(Number, unsigned)", "f is normalized");
bool neg = false;
if (f < zero)
{
@@ -1020,15 +1122,33 @@ root(Number f, unsigned d)
// return r * 10^(e/d) to reverse scaling
auto const result = r.shiftExponent(e / di);
XRPL_ASSERT_PARTS(result.isnormal(), "xrpl::root(Number, unsigned)", "result is normalized");
XRPL_ASSERT_PARTS(
result.isnormal(range), "xrpl::root(Number, unsigned)", "result is normalized");
return result;
}
// Returns f^(1/d)
// Uses NewtonRaphson iterations until the result stops changing
// to find the non-negative root of the polynomial g(x) = x^d - f
// This function, and power(Number f, unsigned n, unsigned d)
// treat corner cases such as 0 roots as advised by Annex F of
// the C standard, which itself is consistent with the IEEE
// floating point standards.
Number
root(Number f, unsigned d)
{
auto const& range = Number::range_.get();
return Number::root(range, f, d);
}
Number
root2(Number f)
{
auto const& range = Number::range_.get();
constexpr Number zero = Number{};
auto const one = Number::one();
auto const one = Number::one(range);
if (f == one)
return f;
@@ -1037,12 +1157,18 @@ root2(Number f)
if (f == zero)
return f;
// Scale f into the range (0, 1) such that f's exponent is a multiple of d
auto e = f.exponent_ + Number::mantissaLog() + 1;
if (e % 2 != 0)
++e;
f = f.shiftExponent(-e); // f /= 10^e;
XRPL_ASSERT_PARTS(f.isnormal(), "xrpl::root2(Number)", "f is normalized");
auto const e = [&]() {
auto const exponent = std::get<2>(f.toInternal(range));
// Scale f into the range (0, 1) such that f's exponent is a
// multiple of d
auto e = exponent + range.log + 1;
if (e % 2 != 0)
++e;
f = f.shiftExponent(-e); // f /= 10^e;
return e;
}();
XRPL_ASSERT_PARTS(f.isnormal(range), "xrpl::root2(Number)", "f is normalized");
// Quadratic least squares curve fit of f^(1/d) in the range [0, 1]
auto const D = 105;
@@ -1064,7 +1190,7 @@ root2(Number f)
// return r * 10^(e/2) to reverse scaling
auto const result = r.shiftExponent(e / 2);
XRPL_ASSERT_PARTS(result.isnormal(), "xrpl::root2(Number)", "result is normalized");
XRPL_ASSERT_PARTS(result.isnormal(range), "xrpl::root2(Number)", "result is normalized");
return result;
}
@@ -1074,8 +1200,10 @@ root2(Number f)
Number
power(Number const& f, unsigned n, unsigned d)
{
auto const& range = Number::range_.get();
constexpr Number zero = Number{};
auto const one = Number::one();
auto const one = Number::one(range);
if (f == one)
return f;
@@ -1097,7 +1225,7 @@ power(Number const& f, unsigned n, unsigned d)
d /= g;
if ((n % 2) == 1 && (d % 2) == 0 && f < zero)
throw std::overflow_error("Number::power nan");
return root(power(f, n), d);
return Number::root(range, power(f, n), d);
}
} // namespace xrpl

View File

@@ -3,23 +3,19 @@
#include <xrpl/protocol/SOTemplate.h>
#include <xrpl/protocol/jss.h>
#include <vector>
#include <initializer_list>
namespace xrpl {
std::vector<SOElement> const&
LedgerFormats::getCommonFields()
LedgerFormats::LedgerFormats()
{
static auto const commonFields = std::vector<SOElement>{
// Fields shared by all ledger formats:
static std::initializer_list<SOElement> const commonFields{
{sfLedgerIndex, soeOPTIONAL},
{sfLedgerEntryType, soeREQUIRED},
{sfFlags, soeREQUIRED},
};
return commonFields;
}
LedgerFormats::LedgerFormats()
{
#pragma push_macro("UNWRAP")
#undef UNWRAP
#pragma push_macro("LEDGER_ENTRY")
@@ -27,7 +23,7 @@ LedgerFormats::LedgerFormats()
#define UNWRAP(...) __VA_ARGS__
#define LEDGER_ENTRY(tag, value, name, rpcName, fields) \
add(jss::name, tag, UNWRAP fields, getCommonFields());
add(jss::name, tag, UNWRAP fields, commonFields);
#include <xrpl/protocol/detail/ledger_entries.macro>

View File

@@ -2,32 +2,23 @@
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/SOTemplate.h>
#include <algorithm>
#include <cstddef>
#include <initializer_list>
#include <iterator>
#include <stdexcept>
#include <vector>
namespace xrpl {
SOTemplate::SOTemplate(
std::initializer_list<SOElement> uniqueFields,
std::initializer_list<SOElement> commonFields)
: SOTemplate(std::vector(uniqueFields), std::vector(commonFields))
{
}
SOTemplate::SOTemplate(std::vector<SOElement> uniqueFields, std::vector<SOElement> commonFields)
: indices_(SField::getNumFields() + 1, -1) // Unmapped indices == -1
{
// Add all SOElements.
//
elements_ = std::move(uniqueFields);
std::ranges::move(commonFields, std::back_inserter(elements_));
elements_.reserve(uniqueFields.size() + commonFields.size());
elements_.assign(uniqueFields);
elements_.insert(elements_.end(), commonFields);
// Validate and index elements_.
//
for (std::size_t i = 0; i < elements_.size(); ++i)
{
SField const& sField{elements_[i].sField()};

View File

@@ -3,14 +3,14 @@
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/jss.h>
#include <vector>
#include <initializer_list>
namespace xrpl {
std::vector<SOElement> const&
TxFormats::getCommonFields()
TxFormats::TxFormats()
{
static auto const commonFields = std::vector<SOElement>{
// Fields shared by all txFormats:
static std::initializer_list<SOElement> const commonFields{
{sfTransactionType, soeREQUIRED},
{sfFlags, soeOPTIONAL},
{sfSourceTag, soeOPTIONAL},
@@ -29,11 +29,7 @@ TxFormats::getCommonFields()
{sfNetworkID, soeOPTIONAL},
{sfDelegate, soeOPTIONAL},
};
return commonFields;
}
TxFormats::TxFormats()
{
#pragma push_macro("UNWRAP")
#undef UNWRAP
#pragma push_macro("TRANSACTION")
@@ -41,7 +37,7 @@ TxFormats::TxFormats()
#define UNWRAP(...) __VA_ARGS__
#define TRANSACTION(tag, value, name, delegable, amendment, privileges, fields) \
add(jss::name, tag, UNWRAP fields, getCommonFields());
add(jss::name, tag, UNWRAP fields, commonFields);
#include <xrpl/protocol/detail/transactions.macro>

View File

@@ -19,7 +19,7 @@ std::uint32_t
AMMDeposit::getFlagsMask(PreflightContext const& ctx)
{
return tfAMMDepositMask;
return tfDepositMask;
}
NotTEC

View File

@@ -17,7 +17,7 @@ AMMWithdraw::checkExtraFeatures(PreflightContext const& ctx)
std::uint32_t
AMMWithdraw::getFlagsMask(PreflightContext const& ctx)
{
return tfAMMWithdrawMask;
return tfWithdrawMask;
}
NotTEC

View File

@@ -16,11 +16,11 @@ NotTEC
Transactor::invokePreflight<Change>(PreflightContext const& ctx)
{
// 0 means "Allow any flags"
// The check for tfEnableAmendmentMask is gated by LendingProtocol because
// that feature introduced this parameter, and it's not worth adding another
// The check for tfChangeMask is gated by LendingProtocol because that
// feature introduced this parameter, and it's not worth adding another
// amendment just for this.
if (auto const ret =
preflight0(ctx, ctx.rules.enabled(featureLendingProtocol) ? tfEnableAmendmentMask : 0))
preflight0(ctx, ctx.rules.enabled(featureLendingProtocol) ? tfChangeMask : 0))
return ret;
auto account = ctx.tx.getAccountID(sfAccount);

View File

@@ -54,6 +54,12 @@ preflightHelper<MPTIssue>(PreflightContext const& ctx)
return tesSUCCESS;
}
std::uint32_t
Clawback::getFlagsMask(PreflightContext const& ctx)
{
return tfClawbackMask;
}
NotTEC
Clawback::preflight(PreflightContext const& ctx)
{

View File

@@ -5,6 +5,12 @@
namespace xrpl {
std::uint32_t
MPTokenIssuanceDestroy::getFlagsMask(PreflightContext const& ctx)
{
return tfMPTokenIssuanceDestroyMask;
}
NotTEC
MPTokenIssuanceDestroy::preflight(PreflightContext const& ctx)
{

View File

@@ -130,7 +130,7 @@ MPTokenIssuanceSet::checkPermission(ReadView const& view, STTx const& tx)
// this is added in case more flags will be added for MPTokenIssuanceSet
// in the future. Currently unreachable.
if (txFlags & tfMPTokenIssuanceSetMask)
if (txFlags & tfMPTokenIssuanceSetPermissionMask)
return terNO_DELEGATE_PERMISSION; // LCOV_EXCL_LINE
std::unordered_set<GranularPermissionType> granularPermissions;

View File

@@ -7,6 +7,12 @@
namespace xrpl {
std::uint32_t
NFTokenAcceptOffer::getFlagsMask(PreflightContext const& ctx)
{
return tfNFTokenAcceptOfferMask;
}
NotTEC
NFTokenAcceptOffer::preflight(PreflightContext const& ctx)
{

View File

@@ -8,6 +8,12 @@
namespace xrpl {
std::uint32_t
NFTokenCancelOffer::getFlagsMask(PreflightContext const& ctx)
{
return tfNFTokenCancelOfferMask;
}
NotTEC
NFTokenCancelOffer::preflight(PreflightContext const& ctx)
{

View File

@@ -48,8 +48,9 @@ NFTokenMint::getFlagsMask(PreflightContext const& ctx)
// tfTrustLine flag as a way to prevent the attack. But until the
// amendment passes we still need to keep the old behavior available.
std::uint32_t const nfTokenMintMask = ctx.rules.enabled(fixRemoveNFTokenAutoTrustLine)
// if featureDynamicNFT enabled then new flag allowing mutable URI available
? ctx.rules.enabled(featureDynamicNFT) ? tfNFTokenMintMask : tfNFTokenMintMaskWithoutMutable
// if featureDynamicNFT enabled then new flag allowing mutable URI
// available
? ctx.rules.enabled(featureDynamicNFT) ? tfNFTokenMintMaskWithMutable : tfNFTokenMintMask
: ctx.rules.enabled(featureDynamicNFT) ? tfNFTokenMintOldMaskWithMutable
: tfNFTokenMintOldMask;

View File

@@ -378,7 +378,7 @@ PayChanClaim::checkExtraFeatures(PreflightContext const& ctx)
std::uint32_t
PayChanClaim::getFlagsMask(PreflightContext const&)
{
return tfPaymentChannelClaimMask;
return tfPayChanClaimMask;
}
NotTEC

View File

@@ -1448,7 +1448,7 @@ XChainCreateBridge::doApply()
std::uint32_t
BridgeModify::getFlagsMask(PreflightContext const& ctx)
{
return tfXChainModifyBridgeMask;
return tfBridgeModifyMask;
}
NotTEC

View File

@@ -4355,7 +4355,6 @@ public:
run() override
{
using namespace test::jtx;
auto const sa = testable_amendments();
testWithFeats(sa - fixBatchInnerSigs);
testWithFeats(sa);

View File

@@ -32,9 +32,10 @@ public:
test_limits()
{
auto const scale = Number::getMantissaScale();
testcase << "test_limits " << to_string(scale);
bool caught = false;
auto const minMantissa = Number::minMantissa();
testcase << "test_limits " << to_string(scale) << ", " << minMantissa;
bool caught = false;
try
{
Number x = Number{false, minMantissa * 10, 32768, Number::normalized{}};
@@ -58,8 +59,9 @@ public:
__LINE__);
test(Number{false, minMantissa, -32769, Number::normalized{}}, Number{}, __LINE__);
test(
// Use 1501 to force rounding up
Number{false, minMantissa, 32000, Number::normalized{}} * 1'000 +
Number{false, 1'500, 32000, Number::normalized{}},
Number{false, 1'501, 32000, Number::normalized{}},
Number{false, minMantissa + 2, 32003, Number::normalized{}},
__LINE__);
// 9,223,372,036,854,775,808
@@ -168,8 +170,12 @@ public:
{Number{true, 9'999'999'999'999'999'999ULL, -37, Number::normalized{}},
Number{1'000'000'000'000'000'000, -18},
Number{false, 9'999'999'999'999'999'990ULL, -19, Number::normalized{}}},
{Number{Number::maxRep}, Number{6, -1}, Number{Number::maxRep / 10, 1}},
{Number{Number::maxRep - 1}, Number{1, 0}, Number{Number::maxRep}},
{Number{Number::largestMantissa},
Number{6, -1},
Number{Number::largestMantissa / 10, 1}},
{Number{Number::largestMantissa - 1},
Number{1, 0},
Number{Number::largestMantissa}},
// Test extremes
{
// Each Number operand rounds up, so the actual mantissa is
@@ -179,11 +185,18 @@ public:
Number{2, 19},
},
{
// Does not round. Mantissas are going to be > maxRep, so if
// added together as uint64_t's, the result will overflow.
// With addition using uint128_t, there's no problem. After
// normalizing, the resulting mantissa ends up less than
// maxRep.
// Does not round. Mantissas are going to be >
// largestMantissa, so if added together as uint64_t's, the
// result will overflow. With addition using uint128_t,
// there's no problem. After normalizing, the resulting
// mantissa ends up less than largestMantissa.
Number{false, Number::largestMantissa, 0, Number::normalized{}},
Number{false, Number::largestMantissa, 0, Number::normalized{}},
Number{false, Number::largestMantissa * 2, 0, Number::normalized{}},
},
{
// These mantissas round down, so adding them together won't
// have any consequences.
Number{false, 9'999'999'999'999'999'990ULL, 0, Number::normalized{}},
Number{false, 9'999'999'999'999'999'990ULL, 0, Number::normalized{}},
Number{false, 1'999'999'999'999'999'998ULL, 1, Number::normalized{}},
@@ -272,14 +285,16 @@ public:
{Number{1'000'000'000'000'000'001, -18},
Number{1'000'000'000'000'000'000, -18},
Number{1'000'000'000'000'000'000, -36}},
{Number{Number::maxRep}, Number{6, -1}, Number{Number::maxRep - 1}},
{Number{false, Number::maxRep + 1, 0, Number::normalized{}},
{Number{Number::largestMantissa},
Number{6, -1},
Number{Number::largestMantissa - 1}},
{Number{false, Number::largestMantissa + 1, 0, Number::normalized{}},
Number{1, 0},
Number{Number::maxRep / 10 + 1, 1}},
{Number{false, Number::maxRep + 1, 0, Number::normalized{}},
Number{Number::largestMantissa / 10 + 1, 1}},
{Number{false, Number::largestMantissa + 1, 0, Number::normalized{}},
Number{3, 0},
Number{Number::maxRep}},
{power(2, 63), Number{3, 0}, Number{Number::maxRep}},
Number{Number::largestMantissa}},
{power(2, 63), Number{3, 0}, Number{Number::largestMantissa}},
});
auto test = [this](auto const& c) {
for (auto const& [x, y, z] : c)
@@ -296,20 +311,30 @@ public:
test(cLarge);
}
static std::uint64_t
getMaxInternalMantissa()
{
return static_cast<std::uint64_t>(
static_cast<std::int64_t>(power(10, Number::mantissaLog()))) *
10 -
1;
}
void
test_mul()
{
auto const scale = Number::getMantissaScale();
testcase << "test_mul " << to_string(scale);
using Case = std::tuple<Number, Number, Number>;
// Case: Factor 1, Factor 2, Expected product, Line number
using Case = std::tuple<Number, Number, Number, int>;
auto test = [this](auto const& c) {
for (auto const& [x, y, z] : c)
for (auto const& [x, y, z, line] : c)
{
auto const result = x * y;
std::stringstream ss;
ss << x << " * " << y << " = " << result << ". Expected: " << z;
BEAST_EXPECTS(result == z, ss.str());
BEAST_EXPECTS(result == z, ss.str() + " line: " + std::to_string(line));
}
};
auto tests = [&](auto const& cSmall, auto const& cLarge) {
@@ -319,70 +344,97 @@ public:
test(cLarge);
};
auto const maxMantissa = Number::maxMantissa();
auto const maxInternalMantissa = getMaxInternalMantissa();
saveNumberRoundMode save{Number::setround(Number::to_nearest)};
{
auto const cSmall = std::to_array<Case>({
{Number{7}, Number{8}, Number{56}},
{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{2000000000000000, -15}},
Number{2000000000000000, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-2000000000000000, -15}},
Number{-2000000000000000, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{2000000000000000, -15}},
Number{2000000000000000, -15},
__LINE__},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{1000000000000000, -14}},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}},
Number{1000000000000000, -14},
__LINE__},
{Number{1000000000000000, -32768},
Number{1000000000000000, -32768},
Number{0},
__LINE__},
// Maximum mantissa range
{Number{9'999'999'999'999'999, 0},
Number{9'999'999'999'999'999, 0},
Number{9'999'999'999'999'998, 16}},
Number{9'999'999'999'999'998, 16},
__LINE__},
});
auto const cLarge = std::to_array<Case>({
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa
{Number{7}, Number{8}, Number{56}},
{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999862, -18}},
Number{1999999999999999862, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999862, -18}},
Number{-1999999999999999862, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999862, -18}},
Number{1999999999999999862, -18},
__LINE__},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::normalized{}}},
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::normalized{}},
__LINE__},
{Number{1000000000000000000, -32768},
Number{1000000000000000000, -32768},
Number{0}},
Number{0},
__LINE__},
// Items from cSmall expanded for the larger mantissa,
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
// with higher precision
{Number{1414213562373095049, -18},
Number{1414213562373095049, -18},
Number{2000000000000000001, -18}},
Number{2000000000000000001, -18},
__LINE__},
{Number{-1414213562373095048, -18},
Number{1414213562373095048, -18},
Number{-1999999999999999998, -18}},
Number{-1999999999999999998, -18},
__LINE__},
{Number{-1414213562373095048, -18},
Number{-1414213562373095049, -18},
Number{1999999999999999999, -18}},
{Number{3214285714285714278, -18}, Number{3111111111111111119, -18}, Number{10, 0}},
// Maximum mantissa range - rounds up to 1e19
Number{1999999999999999999, -18},
__LINE__},
{Number{3214285714285714278, -18},
Number{3111111111111111119, -18},
Number{10, 0},
__LINE__},
// Maximum internal mantissa range - rounds up to 1e19
{Number{false, maxInternalMantissa, 0, Number::normalized{}},
Number{false, maxInternalMantissa, 0, Number::normalized{}},
Number{1, 38},
__LINE__},
// Maximum actual mantissa range - same as int64 range
{Number{false, maxMantissa, 0, Number::normalized{}},
Number{false, maxMantissa, 0, Number::normalized{}},
Number{1, 38}},
Number{85'070'591'730'234'615'85, 19},
__LINE__},
// Maximum int64 range
{Number{Number::maxRep, 0},
Number{Number::maxRep, 0},
Number{85'070'591'730'234'615'85, 19}},
{Number{Number::largestMantissa, 0},
Number{Number::largestMantissa, 0},
Number{85'070'591'730'234'615'85, 19},
__LINE__},
});
tests(cSmall, cLarge);
}
@@ -390,66 +442,90 @@ public:
testcase << "test_mul " << to_string(Number::getMantissaScale()) << " towards_zero";
{
auto const cSmall = std::to_array<Case>(
{{Number{7}, Number{8}, Number{56}},
{{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999, -15}},
Number{1999999999999999, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999, -15}},
Number{-1999999999999999, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999, -15}},
Number{1999999999999999, -15},
__LINE__},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{9999999999999999, -15}},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}});
Number{9999999999999999, -15},
__LINE__},
{Number{1000000000000000, -32768},
Number{1000000000000000, -32768},
Number{0},
__LINE__}});
auto const cLarge = std::to_array<Case>(
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa
{
{Number{7}, Number{8}, Number{56}},
{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999861, -18}},
Number{1999999999999999861, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999861, -18}},
Number{-1999999999999999861, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999861, -18}},
Number{1999999999999999861, -18},
__LINE__},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{false, 9999999999999999579ULL, -18, Number::normalized{}}},
Number{false, 9999999999999999579ULL, -18, Number::normalized{}},
__LINE__},
{Number{1000000000000000000, -32768},
Number{1000000000000000000, -32768},
Number{0}},
Number{0},
__LINE__},
// Items from cSmall expanded for the larger mantissa,
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
// with higher precision
{Number{1414213562373095049, -18},
Number{1414213562373095049, -18},
Number{2, 0}},
Number{2, 0},
__LINE__},
{Number{-1414213562373095048, -18},
Number{1414213562373095048, -18},
Number{-1999999999999999997, -18}},
Number{-1999999999999999997, -18},
__LINE__},
{Number{-1414213562373095048, -18},
Number{-1414213562373095049, -18},
Number{1999999999999999999, -18}},
Number{1999999999999999999, -18},
__LINE__},
{Number{3214285714285714278, -18},
Number{3111111111111111119, -18},
Number{10, 0}},
// Maximum mantissa range - rounds down to maxMantissa/10e1
Number{10, 0},
__LINE__},
// Maximum internal mantissa range - rounds down to
// maxMantissa/10e1
// 99'999'999'999'999'999'800'000'000'000'000'000'100
{Number{false, maxInternalMantissa, 0, Number::normalized{}},
Number{false, maxInternalMantissa, 0, Number::normalized{}},
Number{false, maxInternalMantissa / 10 - 1, 20, Number::normalized{}},
__LINE__},
// Maximum actual mantissa range - same as int64
{Number{false, maxMantissa, 0, Number::normalized{}},
Number{false, maxMantissa, 0, Number::normalized{}},
Number{false, maxMantissa / 10 - 1, 20, Number::normalized{}}},
Number{85'070'591'730'234'615'84, 19},
__LINE__},
// Maximum int64 range
// 85'070'591'730'234'615'847'396'907'784'232'501'249
{Number{Number::maxRep, 0},
Number{Number::maxRep, 0},
Number{85'070'591'730'234'615'84, 19}},
{Number{Number::largestMantissa, 0},
Number{Number::largestMantissa, 0},
Number{85'070'591'730'234'615'84, 19},
__LINE__},
});
tests(cSmall, cLarge);
}
@@ -457,66 +533,90 @@ public:
testcase << "test_mul " << to_string(Number::getMantissaScale()) << " downward";
{
auto const cSmall = std::to_array<Case>(
{{Number{7}, Number{8}, Number{56}},
{{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999, -15}},
Number{1999999999999999, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-2000000000000000, -15}},
Number{-2000000000000000, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999, -15}},
Number{1999999999999999, -15},
__LINE__},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{9999999999999999, -15}},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}});
Number{9999999999999999, -15},
__LINE__},
{Number{1000000000000000, -32768},
Number{1000000000000000, -32768},
Number{0},
__LINE__}});
auto const cLarge = std::to_array<Case>(
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa
{
{Number{7}, Number{8}, Number{56}},
{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999861, -18}},
Number{1999999999999999861, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999862, -18}},
Number{-1999999999999999862, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999861, -18}},
Number{1999999999999999861, -18},
__LINE__},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::normalized{}}},
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::normalized{}},
__LINE__},
{Number{1000000000000000000, -32768},
Number{1000000000000000000, -32768},
Number{0}},
Number{0},
__LINE__},
// Items from cSmall expanded for the larger mantissa,
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
// with higher precision
{Number{1414213562373095049, -18},
Number{1414213562373095049, -18},
Number{2, 0}},
Number{2, 0},
__LINE__},
{Number{-1414213562373095048, -18},
Number{1414213562373095048, -18},
Number{-1999999999999999998, -18}},
Number{-1999999999999999998, -18},
__LINE__},
{Number{-1414213562373095048, -18},
Number{-1414213562373095049, -18},
Number{1999999999999999999, -18}},
Number{1999999999999999999, -18},
__LINE__},
{Number{3214285714285714278, -18},
Number{3111111111111111119, -18},
Number{10, 0}},
// Maximum mantissa range - rounds down to maxMantissa/10e1
Number{10, 0},
__LINE__},
// Maximum internal mantissa range - rounds down to
// maxInternalMantissa/10-1
// 99'999'999'999'999'999'800'000'000'000'000'000'100
{Number{false, maxInternalMantissa, 0, Number::normalized{}},
Number{false, maxInternalMantissa, 0, Number::normalized{}},
Number{false, maxInternalMantissa / 10 - 1, 20, Number::normalized{}},
__LINE__},
// Maximum external mantissa range - same as INT64_MAX (2^63-1)
{Number{false, maxMantissa, 0, Number::normalized{}},
Number{false, maxMantissa, 0, Number::normalized{}},
Number{false, maxMantissa / 10 - 1, 20, Number::normalized{}}},
Number{85'070'591'730'234'615'84, 19},
__LINE__},
// Maximum int64 range
// 85'070'591'730'234'615'847'396'907'784'232'501'249
{Number{Number::maxRep, 0},
Number{Number::maxRep, 0},
Number{85'070'591'730'234'615'84, 19}},
{Number{Number::largestMantissa, 0},
Number{Number::largestMantissa, 0},
Number{85'070'591'730'234'615'84, 19},
__LINE__},
});
tests(cSmall, cLarge);
}
@@ -524,66 +624,89 @@ public:
testcase << "test_mul " << to_string(Number::getMantissaScale()) << " upward";
{
auto const cSmall = std::to_array<Case>(
{{Number{7}, Number{8}, Number{56}},
{{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{2000000000000000, -15}},
Number{2000000000000000, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999, -15}},
Number{-1999999999999999, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{2000000000000000, -15}},
Number{2000000000000000, -15},
__LINE__},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{1000000000000000, -14}},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}});
Number{1000000000000000, -14},
__LINE__},
{Number{1000000000000000, -32768},
Number{1000000000000000, -32768},
Number{0},
__LINE__}});
auto const cLarge = std::to_array<Case>(
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa
{
{Number{7}, Number{8}, Number{56}},
{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999862, -18}},
Number{1999999999999999862, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999861, -18}},
Number{-1999999999999999861, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999862, -18}},
Number{1999999999999999862, -18},
__LINE__},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{999999999999999958, -17}},
Number{999999999999999958, -17},
__LINE__},
{Number{1000000000000000000, -32768},
Number{1000000000000000000, -32768},
Number{0}},
Number{0},
__LINE__},
// Items from cSmall expanded for the larger mantissa,
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
// with higher precision
{Number{1414213562373095049, -18},
Number{1414213562373095049, -18},
Number{2000000000000000001, -18}},
Number{2000000000000000001, -18},
__LINE__},
{Number{-1414213562373095048, -18},
Number{1414213562373095048, -18},
Number{-1999999999999999997, -18}},
Number{-1999999999999999997, -18},
__LINE__},
{Number{-1414213562373095048, -18},
Number{-1414213562373095049, -18},
Number{2, 0}},
Number{2, 0},
__LINE__},
{Number{3214285714285714278, -18},
Number{3111111111111111119, -18},
Number{1000000000000000001, -17}},
// Maximum mantissa range - rounds up to minMantissa*10
// 1e19*1e19=1e38
Number{1000000000000000001, -17},
__LINE__},
// Maximum internal mantissa range - rounds up to
// minMantissa*10 1e19*1e19=1e38
{Number{false, maxInternalMantissa, 0, Number::normalized{}},
Number{false, maxInternalMantissa, 0, Number::normalized{}},
Number{1, 38},
__LINE__},
// Maximum mantissa range - same as int64
{Number{false, maxMantissa, 0, Number::normalized{}},
Number{false, maxMantissa, 0, Number::normalized{}},
Number{1, 38}},
Number{85'070'591'730'234'615'85, 19},
__LINE__},
// Maximum int64 range
// 85'070'591'730'234'615'847'396'907'784'232'501'249
{Number{Number::maxRep, 0},
Number{Number::maxRep, 0},
Number{85'070'591'730'234'615'85, 19}},
{Number{Number::largestMantissa, 0},
Number{Number::largestMantissa, 0},
Number{85'070'591'730'234'615'85, 19},
__LINE__},
});
tests(cSmall, cLarge);
}
@@ -814,6 +937,8 @@ public:
};
*/
auto const maxInternalMantissa = getMaxInternalMantissa();
auto const cSmall = std::to_array<Case>(
{{Number{2}, 2, Number{1414213562373095049, -18}},
{Number{2'000'000}, 2, Number{1414213562373095049, -15}},
@@ -825,16 +950,16 @@ public:
{Number{0}, 5, Number{0}},
{Number{5625, -4}, 2, Number{75, -2}}});
auto const cLarge = std::to_array<Case>({
{Number{false, Number::maxMantissa() - 9, -1, Number::normalized{}},
{Number{false, maxInternalMantissa - 9, -1, Number::normalized{}},
2,
Number{false, 999'999'999'999'999'999, -9, Number::normalized{}}},
{Number{false, Number::maxMantissa() - 9, 0, Number::normalized{}},
{Number{false, maxInternalMantissa - 9, 0, Number::normalized{}},
2,
Number{false, 3'162'277'660'168'379'330, -9, Number::normalized{}}},
{Number{Number::maxRep},
{Number{Number::largestMantissa},
2,
Number{false, 3'037'000'499'976049692, -9, Number::normalized{}}},
{Number{Number::maxRep},
{Number{Number::largestMantissa},
4,
Number{false, 55'108'98747006743627, -14, Number::normalized{}}},
});
@@ -883,6 +1008,8 @@ public:
}
};
Number const maxInternalMantissa = power(10, Number::mantissaLog()) * 10 - 1;
auto const cSmall = std::to_array<Number>({
Number{2},
Number{2'000'000},
@@ -892,7 +1019,10 @@ public:
Number{5, -1},
Number{0},
Number{5625, -4},
Number{Number::maxRep},
Number{Number::largestMantissa},
maxInternalMantissa,
Number{Number::minMantissa(), 0, Number::unchecked{}},
Number{Number::maxMantissa(), 0, Number::unchecked{}},
});
test(cSmall);
bool caught = false;
@@ -1243,18 +1373,18 @@ public:
case MantissaRange::large:
// Test the edges
// ((exponent < -(28)) || (exponent > -(8)))))
test(Number::min(), "1e-32750");
test(Number::min(), "922337203685477581e-32768");
test(Number::max(), "9223372036854775807e32768");
test(Number::lowest(), "-9223372036854775807e32768");
{
NumberRoundModeGuard mg(Number::towards_zero);
auto const maxMantissa = Number::maxMantissa();
BEAST_EXPECT(maxMantissa == 9'999'999'999'999'999'999ULL);
BEAST_EXPECT(maxMantissa == 9'223'372'036'854'775'807ULL);
test(
Number{false, maxMantissa, 0, Number::normalized{}}, "9999999999999999990");
Number{false, maxMantissa, 0, Number::normalized{}}, "9223372036854775807");
test(
Number{true, maxMantissa, 0, Number::normalized{}}, "-9999999999999999990");
Number{true, maxMantissa, 0, Number::normalized{}}, "-9223372036854775807");
test(
Number{std::numeric_limits<std::int64_t>::max(), 0}, "9223372036854775807");
@@ -1490,7 +1620,7 @@ public:
Number const initalXrp{INITIAL_XRP};
BEAST_EXPECT(initalXrp.exponent() > 0);
Number const maxInt64{Number::maxRep};
Number const maxInt64{Number::largestMantissa};
BEAST_EXPECT(maxInt64.exponent() > 0);
// 85'070'591'730'234'615'865'843'651'857'942'052'864 - 38 digits
BEAST_EXPECT((power(maxInt64, 2) == Number{85'070'591'730'234'62, 22}));
@@ -1507,21 +1637,213 @@ public:
Number const initalXrp{INITIAL_XRP};
BEAST_EXPECT(initalXrp.exponent() <= 0);
Number const maxInt64{Number::maxRep};
Number const maxInt64{Number::largestMantissa};
BEAST_EXPECT(maxInt64.exponent() <= 0);
// 85'070'591'730'234'615'847'396'907'784'232'501'249 - 38 digits
BEAST_EXPECT((power(maxInt64, 2) == Number{85'070'591'730'234'615'85, 19}));
NumberRoundModeGuard mg(Number::towards_zero);
auto const maxMantissa = Number::maxMantissa();
Number const max = Number{false, maxMantissa, 0, Number::normalized{}};
BEAST_EXPECT(max.mantissa() == maxMantissa / 10);
BEAST_EXPECT(max.exponent() == 1);
// 99'999'999'999'999'999'800'000'000'000'000'000'100 - also 38
// digits
BEAST_EXPECT(
(power(max, 2) == Number{false, maxMantissa / 10 - 1, 20, Number::normalized{}}));
{
auto const maxInternalMantissa = getMaxInternalMantissa();
// Rounds down to fit under 2^63
Number const max = Number{false, maxInternalMantissa, 0, Number::normalized{}};
// No alterations by the accessors
BEAST_EXPECT(max.mantissa() == maxInternalMantissa / 10);
BEAST_EXPECT(max.exponent() == 1);
// 99'999'999'999'999'999'800'000'000'000'000'000'100 - also 38
// digits
BEAST_EXPECT(
(power(max, 2) ==
Number{false, maxInternalMantissa / 10 - 1, 20, Number::normalized{}}));
}
{
auto const maxMantissa = Number::maxMantissa();
Number const max = Number{false, maxMantissa, 0, Number::normalized{}};
// No alterations by the accessors
BEAST_EXPECT(max.mantissa() == maxMantissa);
BEAST_EXPECT(max.exponent() == 0);
// 85'070'591'730'234'615'847'396'907'784'232'501'249 - also 38
// digits
BEAST_EXPECT(
(power(max, 2) ==
Number{false, 85'070'591'730'234'615'84, 19, Number::normalized{}}));
}
}
}
void
testNormalizeToRange()
{
// Test edge-cases of normalizeToRange
auto const scale = Number::getMantissaScale();
testcase << "normalizeToRange " << to_string(scale);
auto test = [this](
Number const& n,
auto const rangeMin,
auto const rangeMax,
auto const expectedMantissa,
auto const expectedExponent,
auto const line) {
auto const normalized = n.normalizeToRange(rangeMin, rangeMax);
BEAST_EXPECTS(
normalized.first == expectedMantissa,
"Number " + to_string(n) + " scaled to " + std::to_string(rangeMax) +
". Expected mantissa:" + std::to_string(expectedMantissa) +
", got: " + std::to_string(normalized.first) + " @ " + std::to_string(line));
BEAST_EXPECTS(
normalized.second == expectedExponent,
"Number " + to_string(n) + " scaled to " + std::to_string(rangeMax) +
". Expected exponent:" + std::to_string(expectedExponent) +
", got: " + std::to_string(normalized.second) + " @ " + std::to_string(line));
};
std::int64_t constexpr iRangeMin = 100;
std::int64_t constexpr iRangeMax = 999;
std::uint64_t constexpr uRangeMin = 100;
std::uint64_t constexpr uRangeMax = 999;
constexpr static MantissaRange largeRange{MantissaRange::large};
std::int64_t constexpr iBigMin = largeRange.min;
std::int64_t constexpr iBigMax = largeRange.max;
auto const testSuite = [&](Number const& n,
auto const expectedSmallMantissa,
auto const expectedSmallExponent,
auto const expectedLargeMantissa,
auto const expectedLargeExponent,
auto const line) {
test(n, iRangeMin, iRangeMax, expectedSmallMantissa, expectedSmallExponent, line);
test(n, iBigMin, iBigMax, expectedLargeMantissa, expectedLargeExponent, line);
// Only test non-negative. testing a negative number with an
// unsigned range will assert, and asserts can't be tested.
if (n.signum() >= 0)
{
test(n, uRangeMin, uRangeMax, expectedSmallMantissa, expectedSmallExponent, line);
test(
n,
largeRange.min,
largeRange.max,
expectedLargeMantissa,
expectedLargeExponent,
line);
}
};
{
// zero
Number const n{0};
testSuite(
n,
0,
std::numeric_limits<int>::lowest(),
0,
std::numeric_limits<int>::lowest(),
__LINE__);
}
{
// Small positive number
Number const n{2};
testSuite(n, 200, -2, 2'000'000'000'000'000'000, -18, __LINE__);
}
{
// Negative number
Number const n{-2};
testSuite(n, -200, -2, -2'000'000'000'000'000'000, -18, __LINE__);
}
{
// Biggest valid mantissa
Number const n{Number::largestMantissa, 0, Number::normalized{}};
if (scale == MantissaRange::small)
// With the small mantissa range, the value rounds up. Because
// it rounds up, when scaling up to the full int64 range, it
// can't go over the max, so it is one digit smaller than the
// full value.
testSuite(n, 922, 16, 922'337'203'685'477'600, 1, __LINE__);
else
testSuite(n, 922, 16, Number::largestMantissa, 0, __LINE__);
}
{
// Biggest valid mantissa + 1
Number const n{Number::largestMantissa + 1, 0, Number::normalized{}};
if (scale == MantissaRange::small)
// With the small mantissa range, the value rounds up. Because
// it rounds up, when scaling up to the full int64 range, it
// can't go over the max, so it is one digit smaller than the
// full value.
testSuite(n, 922, 16, 922'337'203'685'477'600, 1, __LINE__);
else
testSuite(n, 922, 16, 922'337'203'685'477'581, 1, __LINE__);
}
{
// Biggest valid mantissa + 2
Number const n{Number::largestMantissa + 2, 0, Number::normalized{}};
if (scale == MantissaRange::small)
// With the small mantissa range, the value rounds up. Because
// it rounds up, when scaling up to the full int64 range, it
// can't go over the max, so it is one digit smaller than the
// full value.
testSuite(n, 922, 16, 922'337'203'685'477'600, 1, __LINE__);
else
testSuite(n, 922, 16, 922'337'203'685'477'581, 1, __LINE__);
}
{
// Biggest valid mantissa + 3
Number const n{Number::largestMantissa + 3, 0, Number::normalized{}};
if (scale == MantissaRange::small)
// With the small mantissa range, the value rounds up. Because
// it rounds up, when scaling up to the full int64 range, it
// can't go over the max, so it is one digit smaller than the
// full value.
testSuite(n, 922, 16, 922'337'203'685'477'600, 1, __LINE__);
else
testSuite(n, 922, 16, 922'337'203'685'477'581, 1, __LINE__);
}
{
// int64 min
Number const n{std::numeric_limits<std::int64_t>::min(), 0};
if (scale == MantissaRange::small)
testSuite(n, -922, 16, -922'337'203'685'477'600, 1, __LINE__);
else
testSuite(n, -922, 16, -922'337'203'685'477'581, 1, __LINE__);
}
{
// int64 min + 1
Number const n{std::numeric_limits<std::int64_t>::min() + 1, 0};
if (scale == MantissaRange::small)
testSuite(n, -922, 16, -922'337'203'685'477'600, 1, __LINE__);
else
testSuite(n, -922, 16, -9'223'372'036'854'775'807, 0, __LINE__);
}
{
// int64 min - 1
// Need to cast to uint, even though we're dealing with a negative
// number to avoid overflow and UB
Number const n{
true,
-static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::min()) + 1,
0,
Number::normalized{}};
if (scale == MantissaRange::small)
testSuite(n, -922, 16, -922'337'203'685'477'600, 1, __LINE__);
else
testSuite(n, -922, 16, -922'337'203'685'477'581, 1, __LINE__);
}
}
@@ -1552,6 +1874,7 @@ public:
test_truncate();
testRounding();
testInt64();
testNormalizeToRange();
}
}
};

View File

@@ -575,6 +575,6 @@ public:
}
};
BEAST_DEFINE_TESTSUITE_PRIO(AccountSet, app, xrpl, 1);
BEAST_DEFINE_TESTSUITE_PRIO(AccountSet, rpc, xrpl, 1);
} // namespace xrpl

View File

@@ -1,9 +1,6 @@
#include <test/jtx.h>
#include <xrpl/beast/unit_test.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SOTemplate.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/jss.h>
namespace xrpl {
@@ -84,371 +81,43 @@ public:
BEAST_EXPECT(types["Hash384"].asUInt() == 22);
BEAST_EXPECT(types["Hash512"].asUInt() == 23);
}
// test the properties of the LEDGER_ENTRY_FLAGS section
{
BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_FLAGS));
Json::Value const& leFlags = result[jss::result][jss::LEDGER_ENTRY_FLAGS];
// sanity test the mapped value of a few arbitrarily chosen flags
BEAST_EXPECT(leFlags["AccountRoot"]["lsfDisallowXRP"] == 0x00080000);
BEAST_EXPECT(leFlags["AccountRoot"]["lsfDepositAuth"] == 0x01000000);
BEAST_EXPECT(leFlags["AccountRoot"]["lsfAllowTrustLineClawback"] == 0x80000000);
BEAST_EXPECT(leFlags["RippleState"]["lsfHighFreeze"] == 0x00800000);
BEAST_EXPECT(leFlags["RippleState"]["lsfAMMNode"] == 0x01000000);
BEAST_EXPECT(leFlags["DirNode"]["lsfNFTokenBuyOffers"] == 0x00000001);
BEAST_EXPECT(leFlags["MPTokenIssuance"]["lsfMPTCanTrade"] == 0x00000010);
BEAST_EXPECT(leFlags["Credential"]["lsfAccepted"] == 0x00010000);
BEAST_EXPECT(leFlags["Loan"]["lsfLoanImpaired"] == 0x00020000);
BEAST_EXPECT(leFlags["Vault"]["lsfVaultPrivate"] == 0x00010000);
BEAST_EXPECT(leFlags["MPToken"]["lsfMPTAuthorized"] == 0x00000002);
}
// validate the correctness of few chosen transaction flags
{
BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_FLAGS));
Json::Value const& txFlags = result[jss::result][jss::TRANSACTION_FLAGS];
BEAST_EXPECT(txFlags["universal"]["tfFullyCanonicalSig"] == 0x80000000);
BEAST_EXPECT(txFlags["universal"]["tfInnerBatchTxn"] == 0x40000000);
BEAST_EXPECT(txFlags["AccountSet"]["tfRequireAuth"] == 0x00040000);
BEAST_EXPECT(txFlags["AccountSet"]["tfAllowXRP"] == 0x00200000);
BEAST_EXPECT(txFlags["MPTokenIssuanceSet"]["tfMPTLock"] == 0x00000001);
BEAST_EXPECT(txFlags["MPTokenIssuanceSet"]["tfMPTUnlock"] == 0x00000002);
BEAST_EXPECT(txFlags["AMMDeposit"]["tfLPToken"] == 0x00010000);
BEAST_EXPECT(txFlags["AMMDeposit"]["tfLimitLPToken"] == 0x00400000);
}
// validate the correctness of the AccountSpecificFlags section
{
BEAST_EXPECT(result[jss::result].isMember(jss::ACCOUNT_SET_FLAGS));
Json::Value const& asFlags = result[jss::result][jss::ACCOUNT_SET_FLAGS];
BEAST_EXPECT(asFlags["asfDisallowXRP"] == 3);
BEAST_EXPECT(asFlags["asfGlobalFreeze"] == 7);
BEAST_EXPECT(asFlags["asfDisallowIncomingNFTokenOffer"] == 12);
BEAST_EXPECT(asFlags["asfDisallowIncomingTrustline"] == 15);
}
// test the response fields of the TRANSACTION_FORMATS section
{
BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_FORMATS));
Json::Value const& txnFormats = result[jss::result][jss::TRANSACTION_FORMATS];
// first validate the contents of "common"
{
BEAST_EXPECT(txnFormats.isMember("common"));
Json::Value const& section = txnFormats["common"];
BEAST_EXPECT(section[0u][jss::name] == "TransactionType");
BEAST_EXPECT(section[0u][jss::optionality] == soeREQUIRED);
BEAST_EXPECT(section[1u][jss::name] == "Flags");
BEAST_EXPECT(section[1u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[2u][jss::name] == "SourceTag");
BEAST_EXPECT(section[2u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[3u][jss::name] == "Account");
BEAST_EXPECT(section[3u][jss::optionality] == soeREQUIRED);
BEAST_EXPECT(section[4u][jss::name] == "Sequence");
BEAST_EXPECT(section[4u][jss::optionality] == soeREQUIRED);
BEAST_EXPECT(section[5u][jss::name] == "PreviousTxnID");
BEAST_EXPECT(section[5u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[6u][jss::name] == "LastLedgerSequence");
BEAST_EXPECT(section[6u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[7u][jss::name] == "AccountTxnID");
BEAST_EXPECT(section[7u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[8u][jss::name] == "Fee");
BEAST_EXPECT(section[8u][jss::optionality] == soeREQUIRED);
BEAST_EXPECT(section[9u][jss::name] == "OperationLimit");
BEAST_EXPECT(section[9u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[10u][jss::name] == "Memos");
BEAST_EXPECT(section[10u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[11u][jss::name] == "SigningPubKey");
BEAST_EXPECT(section[11u][jss::optionality] == soeREQUIRED);
BEAST_EXPECT(section[12u][jss::name] == "TicketSequence");
BEAST_EXPECT(section[12u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[13u][jss::name] == "TxnSignature");
BEAST_EXPECT(section[13u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[14u][jss::name] == "Signers");
BEAST_EXPECT(section[14u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[15u][jss::name] == "NetworkID");
BEAST_EXPECT(section[15u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[16u][jss::name] == "Delegate");
BEAST_EXPECT(section[16u][jss::optionality] == soeOPTIONAL);
}
// validate the contents of four arbitrarily selected transactions validate the
// format of the OracleSet transaction
{
BEAST_EXPECT(txnFormats.isMember("OracleSet"));
Json::Value const& section = txnFormats["OracleSet"];
BEAST_EXPECT(section[0u][jss::name] == "OracleDocumentID");
BEAST_EXPECT(section[0u][jss::optionality] == soeREQUIRED);
BEAST_EXPECT(section[1u][jss::name] == "Provider");
BEAST_EXPECT(section[1u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[2u][jss::name] == "URI");
BEAST_EXPECT(section[2u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[3u][jss::name] == "AssetClass");
BEAST_EXPECT(section[3u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[4u][jss::name] == "LastUpdateTime");
BEAST_EXPECT(section[4u][jss::optionality] == soeREQUIRED);
BEAST_EXPECT(section[5u][jss::name] == "PriceDataSeries");
BEAST_EXPECT(section[5u][jss::optionality] == soeREQUIRED);
}
// validate the format of the PermissionedDomainDelete transaction
{
BEAST_EXPECT(txnFormats.isMember("PermissionedDomainDelete"));
Json::Value const& section = txnFormats["PermissionedDomainDelete"];
BEAST_EXPECT(section[0u][jss::name] == "DomainID");
BEAST_EXPECT(section[0u][jss::optionality] == soeREQUIRED);
}
// validate the format of the Clawback transaction
{
BEAST_EXPECT(txnFormats.isMember("Clawback"));
Json::Value const& section = txnFormats["Clawback"];
BEAST_EXPECT(section[0u][jss::name] == "Amount");
BEAST_EXPECT(section[0u][jss::optionality] == soeREQUIRED);
BEAST_EXPECT(section[1u][jss::name] == "Holder");
BEAST_EXPECT(section[1u][jss::optionality] == soeOPTIONAL);
}
// validate the format of the SetFee transaction
{
BEAST_EXPECT(txnFormats.isMember("SetFee"));
Json::Value const& section = txnFormats["SetFee"];
BEAST_EXPECT(section[0u][jss::name] == "LedgerSequence");
BEAST_EXPECT(section[0u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[1u][jss::name] == "BaseFee");
BEAST_EXPECT(section[1u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[2u][jss::name] == "ReferenceFeeUnits");
BEAST_EXPECT(section[2u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[3u][jss::name] == "ReserveBase");
BEAST_EXPECT(section[3u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[4u][jss::name] == "ReserveIncrement");
BEAST_EXPECT(section[4u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[5u][jss::name] == "BaseFeeDrops");
BEAST_EXPECT(section[5u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[6u][jss::name] == "ReserveBaseDrops");
BEAST_EXPECT(section[6u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[7u][jss::name] == "ReserveIncrementDrops");
BEAST_EXPECT(section[7u][jss::optionality] == soeOPTIONAL);
}
}
// test the properties of the LEDGER_ENTRY_FORMATS section in server_definitions
// response
{
BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_FORMATS));
// Note: For the purposes of software maintenance, this test does not exhaustively
// validate all the LEDGER_ENTRY_FORMATS
// check "common" first
{
Json::Value const& observedCommonLedgerEntry =
result[jss::result][jss::LEDGER_ENTRY_FORMATS]["common"];
BEAST_EXPECT(observedCommonLedgerEntry[0u][jss::name] == "LedgerIndex");
BEAST_EXPECT(observedCommonLedgerEntry[0u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(observedCommonLedgerEntry[1u][jss::name] == "LedgerEntryType");
BEAST_EXPECT(observedCommonLedgerEntry[1u][jss::optionality] == soeREQUIRED);
BEAST_EXPECT(observedCommonLedgerEntry[2u][jss::name] == "Flags");
BEAST_EXPECT(observedCommonLedgerEntry[2u][jss::optionality] == soeREQUIRED);
}
// test the contents of an arbitrary ledger-entry (DID)
{
Json::Value const& observedDIDLedgerEntry =
result[jss::result][jss::LEDGER_ENTRY_FORMATS]["DID"];
BEAST_EXPECT(observedDIDLedgerEntry[0u][jss::name] == "Account");
BEAST_EXPECT(observedDIDLedgerEntry[0u][jss::optionality] == soeREQUIRED);
BEAST_EXPECT(observedDIDLedgerEntry[1u][jss::name] == "DIDDocument");
BEAST_EXPECT(observedDIDLedgerEntry[1u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(observedDIDLedgerEntry[2u][jss::name] == "URI");
BEAST_EXPECT(observedDIDLedgerEntry[2u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(observedDIDLedgerEntry[3u][jss::name] == "Data");
BEAST_EXPECT(observedDIDLedgerEntry[3u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(observedDIDLedgerEntry[4u][jss::name] == "OwnerNode");
BEAST_EXPECT(observedDIDLedgerEntry[4u][jss::optionality] == soeREQUIRED);
BEAST_EXPECT(observedDIDLedgerEntry[5u][jss::name] == "PreviousTxnID");
BEAST_EXPECT(observedDIDLedgerEntry[5u][jss::optionality] == soeREQUIRED);
BEAST_EXPECT(observedDIDLedgerEntry[6u][jss::name] == "PreviousTxnLgrSeq");
BEAST_EXPECT(observedDIDLedgerEntry[6u][jss::optionality] == soeREQUIRED);
}
// test the contents of an arbitrary ledger-entry (NegativeUNL)
{
Json::Value const& observedNunlLedgerEntry =
result[jss::result][jss::LEDGER_ENTRY_FORMATS]["NegativeUNL"];
BEAST_EXPECT(observedNunlLedgerEntry[0u][jss::name] == "DisabledValidators");
BEAST_EXPECT(observedNunlLedgerEntry[0u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(observedNunlLedgerEntry[1u][jss::name] == "ValidatorToDisable");
BEAST_EXPECT(observedNunlLedgerEntry[1u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(observedNunlLedgerEntry[2u][jss::name] == "ValidatorToReEnable");
BEAST_EXPECT(observedNunlLedgerEntry[2u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(observedNunlLedgerEntry[3u][jss::name] == "PreviousTxnID");
BEAST_EXPECT(observedNunlLedgerEntry[3u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(observedNunlLedgerEntry[4u][jss::name] == "PreviousTxnLgrSeq");
BEAST_EXPECT(observedNunlLedgerEntry[4u][jss::optionality] == soeOPTIONAL);
}
}
// Exhaustive test: verify all transaction flags from getAllTxFlags() appear in the
// output
{
Json::Value const& txFlags = result[jss::result][jss::TRANSACTION_FLAGS];
for (auto const& [txName, flagMap] : getAllTxFlags())
{
BEAST_EXPECT(txFlags.isMember(txName));
if (txFlags.isMember(txName))
{
for (auto const& [flagName, flagValue] : flagMap)
{
BEAST_EXPECT(txFlags[txName].isMember(flagName));
if (txFlags[txName].isMember(flagName))
{
BEAST_EXPECT(txFlags[txName][flagName].asUInt() == flagValue);
}
}
}
}
}
// Exhaustive test: verify all ledger entry flags from getAllLedgerFlags() appear in the
// output
{
Json::Value const& leFlags = result[jss::result][jss::LEDGER_ENTRY_FLAGS];
for (auto const& [ledgerType, flagMap] : getAllLedgerFlags())
{
BEAST_EXPECT(leFlags.isMember(ledgerType));
if (leFlags.isMember(ledgerType))
{
for (auto const& [flagName, flagValue] : flagMap)
{
BEAST_EXPECT(leFlags[ledgerType].isMember(flagName));
if (leFlags[ledgerType].isMember(flagName))
{
BEAST_EXPECT(leFlags[ledgerType][flagName].asUInt() == flagValue);
}
}
}
}
}
// Exhaustive test: verify all AccountSet flags from getAsfFlagMap() appear in the
// output
{
Json::Value const& asFlags = result[jss::result][jss::ACCOUNT_SET_FLAGS];
for (auto const& [flagName, flagValue] : getAsfFlagMap())
{
BEAST_EXPECT(asFlags.isMember(flagName));
if (asFlags.isMember(flagName))
{
BEAST_EXPECT(asFlags[flagName].asInt() == flagValue);
}
}
}
// test providing the same hash
{
Env env(*this);
auto const firstResult = env.rpc("server_definitions");
auto const hash = firstResult[jss::result][jss::hash].asString();
auto const hashParam = std::string("{ ") + "\"hash\": \"" + hash + "\"}";
auto const result = env.rpc("json", "server_definitions", hashParam);
BEAST_EXPECT(!result[jss::result].isMember(jss::error));
BEAST_EXPECT(result[jss::result][jss::status] == "success");
BEAST_EXPECT(!result[jss::result].isMember(jss::FIELDS));
BEAST_EXPECT(!result[jss::result].isMember(jss::LEDGER_ENTRY_TYPES));
BEAST_EXPECT(!result[jss::result].isMember(jss::LEDGER_ENTRY_FLAGS));
BEAST_EXPECT(!result[jss::result].isMember(jss::LEDGER_ENTRY_FORMATS));
BEAST_EXPECT(!result[jss::result].isMember(jss::TRANSACTION_RESULTS));
BEAST_EXPECT(!result[jss::result].isMember(jss::TRANSACTION_TYPES));
BEAST_EXPECT(!result[jss::result].isMember(jss::TRANSACTION_FLAGS));
BEAST_EXPECT(!result[jss::result].isMember(jss::TRANSACTION_FORMATS));
BEAST_EXPECT(!result[jss::result].isMember(jss::TYPES));
BEAST_EXPECT(result[jss::result].isMember(jss::hash));
}
// test providing a different hash
{
Env env(*this);
std::string const hash =
"54296160385A27154BFA70A239DD8E8FD4CC2DB7BA32D970BA3A5B132CF749"
"D1";
auto const hashParam = std::string("{ ") + "\"hash\": \"" + hash + "\"}";
auto const result = env.rpc("json", "server_definitions", hashParam);
BEAST_EXPECT(!result[jss::result].isMember(jss::error));
BEAST_EXPECT(result[jss::result][jss::status] == "success");
BEAST_EXPECT(result[jss::result].isMember(jss::FIELDS));
BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_TYPES));
BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_FLAGS));
BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_FORMATS));
BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_RESULTS));
BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_TYPES));
BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_FLAGS));
BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_FORMATS));
BEAST_EXPECT(result[jss::result].isMember(jss::TYPES));
BEAST_EXPECT(result[jss::result].isMember(jss::hash));
}
}
// test providing the same hash
{
Env env(*this);
auto const firstResult = env.rpc("server_definitions");
auto const hash = firstResult[jss::result][jss::hash].asString();
auto const hashParam = std::string("{ ") + "\"hash\": \"" + hash + "\"}";
auto const result = env.rpc("json", "server_definitions", hashParam);
BEAST_EXPECT(!result[jss::result].isMember(jss::error));
BEAST_EXPECT(result[jss::result][jss::status] == "success");
BEAST_EXPECT(!result[jss::result].isMember(jss::FIELDS));
BEAST_EXPECT(!result[jss::result].isMember(jss::LEDGER_ENTRY_TYPES));
BEAST_EXPECT(!result[jss::result].isMember(jss::TRANSACTION_RESULTS));
BEAST_EXPECT(!result[jss::result].isMember(jss::TRANSACTION_TYPES));
BEAST_EXPECT(!result[jss::result].isMember(jss::TYPES));
BEAST_EXPECT(result[jss::result].isMember(jss::hash));
}
// test providing a different hash
{
Env env(*this);
std::string const hash =
"54296160385A27154BFA70A239DD8E8FD4CC2DB7BA32D970BA3A5B132CF749"
"D1";
auto const hashParam = std::string("{ ") + "\"hash\": \"" + hash + "\"}";
auto const result = env.rpc("json", "server_definitions", hashParam);
BEAST_EXPECT(!result[jss::result].isMember(jss::error));
BEAST_EXPECT(result[jss::result][jss::status] == "success");
BEAST_EXPECT(result[jss::result].isMember(jss::FIELDS));
BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_TYPES));
BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_RESULTS));
BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_TYPES));
BEAST_EXPECT(result[jss::result].isMember(jss::TYPES));
BEAST_EXPECT(result[jss::result].isMember(jss::hash));
}
}

View File

@@ -6,15 +6,12 @@
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/digest.h>
#include <xrpl/protocol/jss.h>
#include <boost/algorithm/string.hpp>
#include <set>
#include <string_view>
#include <unordered_map>
namespace xrpl {
@@ -50,14 +47,13 @@ public:
std::string
ServerDefinitions::translate(std::string const& inp)
{
auto replace = [&](std::string_view oldStr, std::string_view newStr) -> std::string {
auto replace = [&](char const* oldStr, char const* newStr) -> std::string {
std::string out = inp;
boost::replace_all(out, oldStr, newStr);
return out;
};
// TODO: use string::contains with C++23
auto contains = [&](std::string_view s) -> bool { return inp.find(s) != std::string::npos; };
auto contains = [&](char const* s) -> bool { return inp.find(s) != std::string::npos; };
if (contains("UINT"))
{
@@ -68,7 +64,7 @@ ServerDefinitions::translate(std::string const& inp)
return replace("UINT", "UInt");
}
static std::unordered_map<std::string_view, std::string_view> const replacements{
std::unordered_map<std::string, std::string> replacements{
{"OBJECT", "STObject"},
{"ARRAY", "STArray"},
{"ACCOUNT", "AccountID"},
@@ -81,7 +77,7 @@ ServerDefinitions::translate(std::string const& inp)
if (auto const& it = replacements.find(inp); it != replacements.end())
{
return std::string(it->second);
return it->second;
}
std::string out;
@@ -215,35 +211,36 @@ ServerDefinitions::ServerDefinitions() : defs_{Json::objectValue}
defs_[jss::FIELDS][i++] = a;
}
for (auto const& [code, field] : xrpl::SField::getKnownCodeToField())
for (auto const& [code, f] : xrpl::SField::getKnownCodeToField())
{
if (field->fieldName == "")
if (f->fieldName == "")
continue;
Json::Value innerObj = Json::objectValue;
uint32_t type = field->fieldType;
uint32_t type = f->fieldType;
innerObj[jss::nth] = field->fieldValue;
innerObj[jss::nth] = f->fieldValue;
// whether the field is variable-length encoded this means that the length is included
// before the content
// whether the field is variable-length encoded
// this means that the length is included before the content
innerObj[jss::isVLEncoded] =
(type == 7U /* Blob */ || type == 8U /* AccountID */ || type == 19U /* Vector256 */);
(type == 7U /* Blob */ || type == 8U /* AccountID */ ||
type == 19U /* Vector256 */);
// whether the field is included in serialization
innerObj[jss::isSerialized] =
(type < 10000 && field->fieldName != "hash" &&
field->fieldName !=
"index"); // hash, index, TRANSACTION, LEDGER_ENTRY, VALIDATION, METADATA
(type < 10000 && f->fieldName != "hash" &&
f->fieldName != "index"); /* hash, index, TRANSACTION,
LEDGER_ENTRY, VALIDATION, METADATA */
// whether the field is included in serialization when signing
innerObj[jss::isSigningField] = field->shouldInclude(false);
innerObj[jss::isSigningField] = f->shouldInclude(false);
innerObj[jss::type] = typeMap[type];
Json::Value innerArray = Json::arrayValue;
innerArray[0U] = field->fieldName;
innerArray[0U] = f->fieldName;
innerArray[1U] = innerObj;
defs_[jss::FIELDS][i++] = innerArray;
@@ -265,92 +262,6 @@ ServerDefinitions::ServerDefinitions() : defs_{Json::objectValue}
defs_[jss::TRANSACTION_TYPES][f.getName()] = f.getType();
}
// populate TxFormats
defs_[jss::TRANSACTION_FORMATS] = Json::objectValue;
defs_[jss::TRANSACTION_FORMATS][jss::common] = Json::arrayValue;
auto txCommonFields = std::set<std::string>();
for (auto const& element : TxFormats::getCommonFields())
{
Json::Value elementObj = Json::objectValue;
elementObj[jss::name] = element.sField().getName();
elementObj[jss::optionality] = element.style();
defs_[jss::TRANSACTION_FORMATS][jss::common].append(elementObj);
txCommonFields.insert(element.sField().getName());
}
for (auto const& format : TxFormats::getInstance())
{
auto const& soTemplate = format.getSOTemplate();
Json::Value templateArray = Json::arrayValue;
for (auto const& element : soTemplate)
{
if (txCommonFields.contains(element.sField().getName()))
continue; // skip common fields, already added
Json::Value elementObj = Json::objectValue;
elementObj[jss::name] = element.sField().getName();
elementObj[jss::optionality] = element.style();
templateArray.append(elementObj);
}
defs_[jss::TRANSACTION_FORMATS][format.getName()] = templateArray;
}
// populate LedgerFormats
defs_[jss::LEDGER_ENTRY_FORMATS] = Json::objectValue;
defs_[jss::LEDGER_ENTRY_FORMATS][jss::common] = Json::arrayValue;
auto ledgerCommonFields = std::set<std::string>();
for (auto const& element : LedgerFormats::getCommonFields())
{
Json::Value elementObj = Json::objectValue;
elementObj[jss::name] = element.sField().getName();
elementObj[jss::optionality] = element.style();
defs_[jss::LEDGER_ENTRY_FORMATS][jss::common].append(elementObj);
ledgerCommonFields.insert(element.sField().getName());
}
for (auto const& format : LedgerFormats::getInstance())
{
auto const& soTemplate = format.getSOTemplate();
Json::Value templateArray = Json::arrayValue;
for (auto const& element : soTemplate)
{
if (ledgerCommonFields.contains(element.sField().getName()))
continue; // skip common fields, already added
Json::Value elementObj = Json::objectValue;
elementObj[jss::name] = element.sField().getName();
elementObj[jss::optionality] = element.style();
templateArray.append(elementObj);
}
defs_[jss::LEDGER_ENTRY_FORMATS][format.getName()] = templateArray;
}
defs_[jss::TRANSACTION_FLAGS] = Json::objectValue;
for (auto const& [name, value] : getAllTxFlags())
{
Json::Value txObj = Json::objectValue;
for (auto const& [flagName, flagValue] : value)
{
txObj[flagName] = flagValue;
}
defs_[jss::TRANSACTION_FLAGS][name] = txObj;
}
defs_[jss::LEDGER_ENTRY_FLAGS] = Json::objectValue;
for (auto const& [name, value] : getAllLedgerFlags())
{
Json::Value ledgerObj = Json::objectValue;
for (auto const& [flagName, flagValue] : value)
{
ledgerObj[flagName] = flagValue;
}
defs_[jss::LEDGER_ENTRY_FLAGS][name] = ledgerObj;
}
defs_[jss::ACCOUNT_SET_FLAGS] = Json::objectValue;
for (auto const& [name, value] : getAsfFlagMap())
{
defs_[jss::ACCOUNT_SET_FLAGS][name] = value;
}
// generate hash
{
std::string const out = Json::FastWriter().write(defs_);